Add authentication, styling needs to be fixed

# Conflicts:
#	starter/ent/runtime.go
#	starter/go.mod
#	starter/go.sum
#	starter/routes/routing.go
This commit is contained in:
Achim Rohn
2026-03-20 20:35:20 +00:00
parent f768e9e47c
commit a15ca501b8
34 changed files with 468 additions and 258 deletions
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"context"
"flag"
"log"
"time"
"entgo.io/ent/dialect"
"git.gorlug.de/code/ersteller/starter/ent"
"git.gorlug.de/code/ersteller/starter/ent/user"
"git.gorlug.de/code/ersteller/starter/env"
"golang.org/x/crypto/bcrypt"
)
// This CLI creates a new user with email and password, or changes an existing user's password.
func main() {
email := flag.String("email", "", "User email (required)")
password := flag.String("password", "", "User password (required)")
changePassword := flag.Bool("change-password", false, "Change password for existing user")
flag.Parse()
if *email == "" || *password == "" {
log.Fatal("Both -email and -password flags are required")
}
environment := env.LoadEnvironment()
// Open mediator database client
dbClient, err := ent.Open(dialect.SQLite, environment.DatabaseUrl, ent.Log(log.Println), ent.Debug())
if err != nil {
log.Fatalf("failed opening mediator DB: %v", err)
}
defer dbClient.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Hash the password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
if err != nil {
log.Fatalf("failed to hash password: %v", err)
}
if *changePassword {
// Change password for existing user
u, err := dbClient.User.
Query().
Where(user.EmailEQ(*email)).
Only(ctx)
if err != nil {
log.Fatalf("failed to find user with email %s: %v", *email, err)
}
u, err = u.
Update().
SetPasswordHash(string(hashedPassword)).
Save(ctx)
if err != nil {
log.Fatalf("failed to update password: %v", err)
}
log.Printf("Successfully changed password for user: ID=%d, Email=%s\n", u.ID, u.Email)
} else {
// Create the user
u, err := dbClient.User.
Create().
SetEmail(*email).
SetPasswordHash(string(hashedPassword)).
Save(ctx)
if err != nil {
log.Fatalf("failed to create user: %v", err)
}
log.Printf("Successfully created user: ID=%d, Email=%s\n", u.ID, u.Email)
}
}
+3 -4
View File
@@ -11,14 +11,13 @@ import (
"git.gorlug.de/code/ersteller/starter/ent/migrate"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// Client is the client that holds all ent builders.
+3 -3
View File
@@ -6,15 +6,15 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"reflect"
"sync"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// ent aliases to avoid import conflicts in user's code.
+1 -2
View File
@@ -9,9 +9,8 @@ import (
// required by schema hooks.
_ "git.gorlug.de/code/ersteller/starter/ent/runtime"
"git.gorlug.de/code/ersteller/starter/ent/migrate"
"entgo.io/ent/dialect/sql/schema"
"git.gorlug.de/code/ersteller/starter/ent/migrate"
)
type (
+3 -3
View File
@@ -5,14 +5,14 @@ package ent
import (
"encoding/json"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/user"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// GoogleAuth is the model entity for the GoogleAuth schema.
+1 -1
View File
@@ -3,11 +3,11 @@
package googleauth
import (
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
)
// ID filters vertices based on their ID field.
+3 -3
View File
@@ -6,13 +6,13 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// GoogleAuthCreate is the builder for creating a GoogleAuth entity.
+2 -2
View File
@@ -4,12 +4,12 @@ package ent
import (
"context"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
)
// GoogleAuthDelete is the builder for deleting a GoogleAuth entity.
+3 -3
View File
@@ -5,15 +5,15 @@ package ent
import (
"context"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// GoogleAuthQuery is the builder for querying GoogleAuth entities.
+4 -4
View File
@@ -6,15 +6,15 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// GoogleAuthUpdate is the builder for updating GoogleAuth entities.
+1
View File
@@ -5,6 +5,7 @@ package hook
import (
"context"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent"
)
+2 -2
View File
@@ -58,8 +58,8 @@ var (
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
{Name: "email", Type: field.TypeString, Default: "unknown@localhost"},
{Name: "password", Type: field.TypeString, Default: ""},
{Name: "email", Type: field.TypeString, Unique: true},
{Name: "password_hash", Type: field.TypeString, Default: ""},
}
// UsersTable holds the schema information for the "users" table.
UsersTable = &schema.Table{
+31 -31
View File
@@ -6,16 +6,16 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"sync"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
const (
@@ -1097,7 +1097,7 @@ type UserMutation struct {
created_at *time.Time
updated_at *time.Time
email *string
password *string
password_hash *string
clearedFields map[string]struct{}
done bool
oldValue func(context.Context) (*User, error)
@@ -1310,40 +1310,40 @@ func (m *UserMutation) ResetEmail() {
m.email = nil
}
// SetPassword sets the "password" field.
func (m *UserMutation) SetPassword(s string) {
m.password = &s
// SetPasswordHash sets the "password_hash" field.
func (m *UserMutation) SetPasswordHash(s string) {
m.password_hash = &s
}
// Password returns the value of the "password" field in the mutation.
func (m *UserMutation) Password() (r string, exists bool) {
v := m.password
// PasswordHash returns the value of the "password_hash" field in the mutation.
func (m *UserMutation) PasswordHash() (r string, exists bool) {
v := m.password_hash
if v == nil {
return
}
return *v, true
}
// OldPassword returns the old "password" field's value of the User entity.
// OldPasswordHash returns the old "password_hash" field's value of the User entity.
// If the User object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *UserMutation) OldPassword(ctx context.Context) (v string, err error) {
func (m *UserMutation) OldPasswordHash(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldPassword is only allowed on UpdateOne operations")
return v, errors.New("OldPasswordHash is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldPassword requires an ID field in the mutation")
return v, errors.New("OldPasswordHash requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldPassword: %w", err)
return v, fmt.Errorf("querying old value for OldPasswordHash: %w", err)
}
return oldValue.Password, nil
return oldValue.PasswordHash, nil
}
// ResetPassword resets all changes to the "password" field.
func (m *UserMutation) ResetPassword() {
m.password = nil
// ResetPasswordHash resets all changes to the "password_hash" field.
func (m *UserMutation) ResetPasswordHash() {
m.password_hash = nil
}
// Where appends a list predicates to the UserMutation builder.
@@ -1390,8 +1390,8 @@ func (m *UserMutation) Fields() []string {
if m.email != nil {
fields = append(fields, user.FieldEmail)
}
if m.password != nil {
fields = append(fields, user.FieldPassword)
if m.password_hash != nil {
fields = append(fields, user.FieldPasswordHash)
}
return fields
}
@@ -1407,8 +1407,8 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) {
return m.UpdatedAt()
case user.FieldEmail:
return m.Email()
case user.FieldPassword:
return m.Password()
case user.FieldPasswordHash:
return m.PasswordHash()
}
return nil, false
}
@@ -1424,8 +1424,8 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er
return m.OldUpdatedAt(ctx)
case user.FieldEmail:
return m.OldEmail(ctx)
case user.FieldPassword:
return m.OldPassword(ctx)
case user.FieldPasswordHash:
return m.OldPasswordHash(ctx)
}
return nil, fmt.Errorf("unknown User field %s", name)
}
@@ -1456,12 +1456,12 @@ func (m *UserMutation) SetField(name string, value ent.Value) error {
}
m.SetEmail(v)
return nil
case user.FieldPassword:
case user.FieldPasswordHash:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetPassword(v)
m.SetPasswordHash(v)
return nil
}
return fmt.Errorf("unknown User field %s", name)
@@ -1521,8 +1521,8 @@ func (m *UserMutation) ResetField(name string) error {
case user.FieldEmail:
m.ResetEmail()
return nil
case user.FieldPassword:
m.ResetPassword()
case user.FieldPasswordHash:
m.ResetPasswordHash()
return nil
}
return fmt.Errorf("unknown User field %s", name)
+6 -9
View File
@@ -3,11 +3,12 @@
package ent
import (
"time"
"git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time"
)
// The init function reads all schema descriptors with runtime code
@@ -67,12 +68,8 @@ func init() {
user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time)
// user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() time.Time)
// userDescEmail is the schema descriptor for email field.
userDescEmail := userFields[0].Descriptor()
// user.DefaultEmail holds the default value on creation for the email field.
user.DefaultEmail = userDescEmail.Default.(string)
// userDescPassword is the schema descriptor for password field.
userDescPassword := userFields[1].Descriptor()
// user.DefaultPassword holds the default value on creation for the password field.
user.DefaultPassword = userDescPassword.Default.(string)
// userDescPasswordHash is the schema descriptor for password_hash field.
userDescPasswordHash := userFields[1].Descriptor()
// user.DefaultPasswordHash holds the default value on creation for the password_hash field.
user.DefaultPasswordHash = userDescPasswordHash.Default.(string)
}
+1 -1
View File
@@ -2,7 +2,7 @@
package runtime
// The schema-stitching logic is generated in ersteller-lib/starter/ent/runtime.go
// The schema-stitching logic is generated in git.gorlug.de/code/ersteller/starter/ent/runtime.go
const (
Version = "v0.14.5" // Version of ent codegen.
+4 -2
View File
@@ -20,7 +20,9 @@ func (User) Mixin() []ent.Mixin {
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("email").
Default("unknown@localhost"),
field.String("password").Default(""),
Unique(),
field.String("password_hash").
Default("").
Sensitive(),
}
}
+2 -2
View File
@@ -4,13 +4,13 @@ package ent
import (
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// Todo is the model entity for the Todo schema.
+1 -1
View File
@@ -3,11 +3,11 @@
package todo
import (
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
)
// ID filters vertices based on their ID field.
+2 -2
View File
@@ -6,12 +6,12 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// TodoCreate is the builder for creating a Todo entity.
+2 -2
View File
@@ -4,12 +4,12 @@ package ent
import (
"context"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/todo"
)
// TodoDelete is the builder for deleting a Todo entity.
+3 -3
View File
@@ -5,15 +5,15 @@ package ent
import (
"context"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// TodoQuery is the builder for querying Todo entities.
+3 -3
View File
@@ -6,14 +6,14 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// TodoUpdate is the builder for updating Todo entities.
+8 -9
View File
@@ -4,12 +4,12 @@ package ent
import (
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/user"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// User is the model entity for the User schema.
@@ -23,8 +23,8 @@ type User struct {
UpdatedAt time.Time `json:"updated_at,omitempty"`
// Email holds the value of the "email" field.
Email string `json:"email,omitempty"`
// Password holds the value of the "password" field.
Password string `json:"password,omitempty"`
// PasswordHash holds the value of the "password_hash" field.
PasswordHash string `json:"-"`
selectValues sql.SelectValues
}
@@ -35,7 +35,7 @@ func (*User) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case user.FieldID:
values[i] = new(sql.NullInt64)
case user.FieldEmail, user.FieldPassword:
case user.FieldEmail, user.FieldPasswordHash:
values[i] = new(sql.NullString)
case user.FieldCreatedAt, user.FieldUpdatedAt:
values[i] = new(sql.NullTime)
@@ -78,11 +78,11 @@ func (_m *User) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.Email = value.String
}
case user.FieldPassword:
case user.FieldPasswordHash:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field password", values[i])
return fmt.Errorf("unexpected type %T for field password_hash", values[i])
} else if value.Valid {
_m.Password = value.String
_m.PasswordHash = value.String
}
default:
_m.selectValues.Set(columns[i], values[i])
@@ -129,8 +129,7 @@ func (_m *User) String() string {
builder.WriteString("email=")
builder.WriteString(_m.Email)
builder.WriteString(", ")
builder.WriteString("password=")
builder.WriteString(_m.Password)
builder.WriteString("password_hash=<sensitive>")
builder.WriteByte(')')
return builder.String()
}
+8 -10
View File
@@ -19,8 +19,8 @@ const (
FieldUpdatedAt = "updated_at"
// FieldEmail holds the string denoting the email field in the database.
FieldEmail = "email"
// FieldPassword holds the string denoting the password field in the database.
FieldPassword = "password"
// FieldPasswordHash holds the string denoting the password_hash field in the database.
FieldPasswordHash = "password_hash"
// Table holds the table name of the user in the database.
Table = "users"
)
@@ -31,7 +31,7 @@ var Columns = []string{
FieldCreatedAt,
FieldUpdatedAt,
FieldEmail,
FieldPassword,
FieldPasswordHash,
}
// ValidColumn reports if the column name is valid (part of the table columns).
@@ -51,10 +51,8 @@ var (
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
// DefaultEmail holds the default value on creation for the "email" field.
DefaultEmail string
// DefaultPassword holds the default value on creation for the "password" field.
DefaultPassword string
// DefaultPasswordHash holds the default value on creation for the "password_hash" field.
DefaultPasswordHash string
)
// OrderOption defines the ordering options for the User queries.
@@ -80,7 +78,7 @@ func ByEmail(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEmail, opts...).ToFunc()
}
// ByPassword orders the results by the password field.
func ByPassword(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPassword, opts...).ToFunc()
// ByPasswordHash orders the results by the password_hash field.
func ByPasswordHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPasswordHash, opts...).ToFunc()
}
+43 -43
View File
@@ -3,10 +3,10 @@
package user
import (
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"time"
"entgo.io/ent/dialect/sql"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
)
// ID filters vertices based on their ID field.
@@ -69,9 +69,9 @@ func Email(v string) predicate.User {
return predicate.User(sql.FieldEQ(FieldEmail, v))
}
// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ.
func Password(v string) predicate.User {
return predicate.User(sql.FieldEQ(FieldPassword, v))
// PasswordHash applies equality check predicate on the "password_hash" field. It's identical to PasswordHashEQ.
func PasswordHash(v string) predicate.User {
return predicate.User(sql.FieldEQ(FieldPasswordHash, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
@@ -219,69 +219,69 @@ func EmailContainsFold(v string) predicate.User {
return predicate.User(sql.FieldContainsFold(FieldEmail, v))
}
// PasswordEQ applies the EQ predicate on the "password" field.
func PasswordEQ(v string) predicate.User {
return predicate.User(sql.FieldEQ(FieldPassword, v))
// PasswordHashEQ applies the EQ predicate on the "password_hash" field.
func PasswordHashEQ(v string) predicate.User {
return predicate.User(sql.FieldEQ(FieldPasswordHash, v))
}
// PasswordNEQ applies the NEQ predicate on the "password" field.
func PasswordNEQ(v string) predicate.User {
return predicate.User(sql.FieldNEQ(FieldPassword, v))
// PasswordHashNEQ applies the NEQ predicate on the "password_hash" field.
func PasswordHashNEQ(v string) predicate.User {
return predicate.User(sql.FieldNEQ(FieldPasswordHash, v))
}
// PasswordIn applies the In predicate on the "password" field.
func PasswordIn(vs ...string) predicate.User {
return predicate.User(sql.FieldIn(FieldPassword, vs...))
// PasswordHashIn applies the In predicate on the "password_hash" field.
func PasswordHashIn(vs ...string) predicate.User {
return predicate.User(sql.FieldIn(FieldPasswordHash, vs...))
}
// PasswordNotIn applies the NotIn predicate on the "password" field.
func PasswordNotIn(vs ...string) predicate.User {
return predicate.User(sql.FieldNotIn(FieldPassword, vs...))
// PasswordHashNotIn applies the NotIn predicate on the "password_hash" field.
func PasswordHashNotIn(vs ...string) predicate.User {
return predicate.User(sql.FieldNotIn(FieldPasswordHash, vs...))
}
// PasswordGT applies the GT predicate on the "password" field.
func PasswordGT(v string) predicate.User {
return predicate.User(sql.FieldGT(FieldPassword, v))
// PasswordHashGT applies the GT predicate on the "password_hash" field.
func PasswordHashGT(v string) predicate.User {
return predicate.User(sql.FieldGT(FieldPasswordHash, v))
}
// PasswordGTE applies the GTE predicate on the "password" field.
func PasswordGTE(v string) predicate.User {
return predicate.User(sql.FieldGTE(FieldPassword, v))
// PasswordHashGTE applies the GTE predicate on the "password_hash" field.
func PasswordHashGTE(v string) predicate.User {
return predicate.User(sql.FieldGTE(FieldPasswordHash, v))
}
// PasswordLT applies the LT predicate on the "password" field.
func PasswordLT(v string) predicate.User {
return predicate.User(sql.FieldLT(FieldPassword, v))
// PasswordHashLT applies the LT predicate on the "password_hash" field.
func PasswordHashLT(v string) predicate.User {
return predicate.User(sql.FieldLT(FieldPasswordHash, v))
}
// PasswordLTE applies the LTE predicate on the "password" field.
func PasswordLTE(v string) predicate.User {
return predicate.User(sql.FieldLTE(FieldPassword, v))
// PasswordHashLTE applies the LTE predicate on the "password_hash" field.
func PasswordHashLTE(v string) predicate.User {
return predicate.User(sql.FieldLTE(FieldPasswordHash, v))
}
// PasswordContains applies the Contains predicate on the "password" field.
func PasswordContains(v string) predicate.User {
return predicate.User(sql.FieldContains(FieldPassword, v))
// PasswordHashContains applies the Contains predicate on the "password_hash" field.
func PasswordHashContains(v string) predicate.User {
return predicate.User(sql.FieldContains(FieldPasswordHash, v))
}
// PasswordHasPrefix applies the HasPrefix predicate on the "password" field.
func PasswordHasPrefix(v string) predicate.User {
return predicate.User(sql.FieldHasPrefix(FieldPassword, v))
// PasswordHashHasPrefix applies the HasPrefix predicate on the "password_hash" field.
func PasswordHashHasPrefix(v string) predicate.User {
return predicate.User(sql.FieldHasPrefix(FieldPasswordHash, v))
}
// PasswordHasSuffix applies the HasSuffix predicate on the "password" field.
func PasswordHasSuffix(v string) predicate.User {
return predicate.User(sql.FieldHasSuffix(FieldPassword, v))
// PasswordHashHasSuffix applies the HasSuffix predicate on the "password_hash" field.
func PasswordHashHasSuffix(v string) predicate.User {
return predicate.User(sql.FieldHasSuffix(FieldPasswordHash, v))
}
// PasswordEqualFold applies the EqualFold predicate on the "password" field.
func PasswordEqualFold(v string) predicate.User {
return predicate.User(sql.FieldEqualFold(FieldPassword, v))
// PasswordHashEqualFold applies the EqualFold predicate on the "password_hash" field.
func PasswordHashEqualFold(v string) predicate.User {
return predicate.User(sql.FieldEqualFold(FieldPasswordHash, v))
}
// PasswordContainsFold applies the ContainsFold predicate on the "password" field.
func PasswordContainsFold(v string) predicate.User {
return predicate.User(sql.FieldContainsFold(FieldPassword, v))
// PasswordHashContainsFold applies the ContainsFold predicate on the "password_hash" field.
func PasswordHashContainsFold(v string) predicate.User {
return predicate.User(sql.FieldContainsFold(FieldPasswordHash, v))
}
// And groups predicates with the AND operator between them.
+15 -27
View File
@@ -6,11 +6,11 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// UserCreate is the builder for creating a User entity.
@@ -54,24 +54,16 @@ func (_c *UserCreate) SetEmail(v string) *UserCreate {
return _c
}
// SetNillableEmail sets the "email" field if the given value is not nil.
func (_c *UserCreate) SetNillableEmail(v *string) *UserCreate {
if v != nil {
_c.SetEmail(*v)
}
// SetPasswordHash sets the "password_hash" field.
func (_c *UserCreate) SetPasswordHash(v string) *UserCreate {
_c.mutation.SetPasswordHash(v)
return _c
}
// SetPassword sets the "password" field.
func (_c *UserCreate) SetPassword(v string) *UserCreate {
_c.mutation.SetPassword(v)
return _c
}
// SetNillablePassword sets the "password" field if the given value is not nil.
func (_c *UserCreate) SetNillablePassword(v *string) *UserCreate {
// SetNillablePasswordHash sets the "password_hash" field if the given value is not nil.
func (_c *UserCreate) SetNillablePasswordHash(v *string) *UserCreate {
if v != nil {
_c.SetPassword(*v)
_c.SetPasswordHash(*v)
}
return _c
}
@@ -119,13 +111,9 @@ func (_c *UserCreate) defaults() {
v := user.DefaultUpdatedAt()
_c.mutation.SetUpdatedAt(v)
}
if _, ok := _c.mutation.Email(); !ok {
v := user.DefaultEmail
_c.mutation.SetEmail(v)
}
if _, ok := _c.mutation.Password(); !ok {
v := user.DefaultPassword
_c.mutation.SetPassword(v)
if _, ok := _c.mutation.PasswordHash(); !ok {
v := user.DefaultPasswordHash
_c.mutation.SetPasswordHash(v)
}
}
@@ -140,8 +128,8 @@ func (_c *UserCreate) check() error {
if _, ok := _c.mutation.Email(); !ok {
return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "User.email"`)}
}
if _, ok := _c.mutation.Password(); !ok {
return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "User.password"`)}
if _, ok := _c.mutation.PasswordHash(); !ok {
return &ValidationError{Name: "password_hash", err: errors.New(`ent: missing required field "User.password_hash"`)}
}
return nil
}
@@ -181,9 +169,9 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
_spec.SetField(user.FieldEmail, field.TypeString, value)
_node.Email = value
}
if value, ok := _c.mutation.Password(); ok {
_spec.SetField(user.FieldPassword, field.TypeString, value)
_node.Password = value
if value, ok := _c.mutation.PasswordHash(); ok {
_spec.SetField(user.FieldPasswordHash, field.TypeString, value)
_node.PasswordHash = value
}
return _node, _spec
}
+2 -2
View File
@@ -4,12 +4,12 @@ package ent
import (
"context"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// UserDelete is the builder for deleting a User entity.
+2 -2
View File
@@ -5,14 +5,14 @@ package ent
import (
"context"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// UserQuery is the builder for querying User entities.
+18 -18
View File
@@ -6,13 +6,13 @@ import (
"context"
"errors"
"fmt"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
)
// UserUpdate is the builder for updating User entities.
@@ -48,16 +48,16 @@ func (_u *UserUpdate) SetNillableEmail(v *string) *UserUpdate {
return _u
}
// SetPassword sets the "password" field.
func (_u *UserUpdate) SetPassword(v string) *UserUpdate {
_u.mutation.SetPassword(v)
// SetPasswordHash sets the "password_hash" field.
func (_u *UserUpdate) SetPasswordHash(v string) *UserUpdate {
_u.mutation.SetPasswordHash(v)
return _u
}
// SetNillablePassword sets the "password" field if the given value is not nil.
func (_u *UserUpdate) SetNillablePassword(v *string) *UserUpdate {
// SetNillablePasswordHash sets the "password_hash" field if the given value is not nil.
func (_u *UserUpdate) SetNillablePasswordHash(v *string) *UserUpdate {
if v != nil {
_u.SetPassword(*v)
_u.SetPasswordHash(*v)
}
return _u
}
@@ -118,8 +118,8 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.Email(); ok {
_spec.SetField(user.FieldEmail, field.TypeString, value)
}
if value, ok := _u.mutation.Password(); ok {
_spec.SetField(user.FieldPassword, field.TypeString, value)
if value, ok := _u.mutation.PasswordHash(); ok {
_spec.SetField(user.FieldPasswordHash, field.TypeString, value)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
@@ -161,16 +161,16 @@ func (_u *UserUpdateOne) SetNillableEmail(v *string) *UserUpdateOne {
return _u
}
// SetPassword sets the "password" field.
func (_u *UserUpdateOne) SetPassword(v string) *UserUpdateOne {
_u.mutation.SetPassword(v)
// SetPasswordHash sets the "password_hash" field.
func (_u *UserUpdateOne) SetPasswordHash(v string) *UserUpdateOne {
_u.mutation.SetPasswordHash(v)
return _u
}
// SetNillablePassword sets the "password" field if the given value is not nil.
func (_u *UserUpdateOne) SetNillablePassword(v *string) *UserUpdateOne {
// SetNillablePasswordHash sets the "password_hash" field if the given value is not nil.
func (_u *UserUpdateOne) SetNillablePasswordHash(v *string) *UserUpdateOne {
if v != nil {
_u.SetPassword(*v)
_u.SetPasswordHash(*v)
}
return _u
}
@@ -261,8 +261,8 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
if value, ok := _u.mutation.Email(); ok {
_spec.SetField(user.FieldEmail, field.TypeString, value)
}
if value, ok := _u.mutation.Password(); ok {
_spec.SetField(user.FieldPassword, field.TypeString, value)
if value, ok := _u.mutation.PasswordHash(); ok {
_spec.SetField(user.FieldPasswordHash, field.TypeString, value)
}
_node = &User{config: _u.config}
_spec.Assign = _node.assignValues
+14 -12
View File
@@ -7,20 +7,22 @@ require (
git.gorlug.de/code/ersteller v0.0.0-20251115110439-18d05566e2fc
github.com/gorilla/sessions v1.4.0
github.com/joho/godotenv v1.5.1
golang.org/x/oauth2 v0.30.0
golang.org/x/crypto v0.44.0
golang.org/x/oauth2 v0.32.0
maragu.dev/gomponents v1.2.0
maragu.dev/gomponents-htmx v0.6.1
)
require (
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
@@ -36,16 +38,16 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/zclconf/go-cty v1.14.4 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/tools v0.36.0 // indirect
maragu.dev/gomponents-htmx v0.6.1 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
)
+26 -26
View File
@@ -1,7 +1,7 @@
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 h1:E0wvcUXTkgyN4wy4LGtNzMNGMytJN8afmIWXJVMi4cc=
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
git.gorlug.de/code/ersteller v0.0.0-20251115110439-18d05566e2fc h1:oin+Mm/Z9FaR0To40MDGrFsEdK7s7FDaC9vImeyvMdg=
@@ -23,12 +23,12 @@ github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNP
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
@@ -71,15 +71,15 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
@@ -88,23 +88,23 @@ github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+144 -21
View File
@@ -1,7 +1,15 @@
package login
import (
"net/http"
. "git.gorlug.de/code/ersteller"
"git.gorlug.de/code/ersteller/authentication"
"git.gorlug.de/code/ersteller/starter/ent"
"git.gorlug.de/code/ersteller/starter/ent/user"
"github.com/gorilla/sessions"
"golang.org/x/crypto/bcrypt"
hx "maragu.dev/gomponents-htmx"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
@@ -9,34 +17,55 @@ import (
const LoginPath = "/login"
const LoginPathDe = "/anmelden"
const LocalLoginPath = "/login/local"
const LocalLoginPathDe = "/anmelden/lokal"
var loginTexts *LoginTexts
type LoginTexts struct {
PageTitle I18nText
PageDescription I18nText
HeroTitle I18nText
HeroDescription I18nText
GoogleLoginBtn I18nText
PageTitle I18nText
PageDescription I18nText
HeroTitle I18nText
HeroDescription I18nText
GoogleLoginBtn I18nText
EmailLabel I18nText
PasswordLabel I18nText
LoginBtn I18nText
OrSeparator I18nText
InvalidCreds I18nText
SessionSaveError I18nText
}
type Page struct {
createPage CreateHtmxPageFunc
ViewRoute HtmxRoute
createPage CreateHtmxPageFunc
ViewRoute HtmxRoute
db *ent.Client
sessionStore *sessions.CookieStore
localLoginRoute HtmxRoute
}
func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath) *Page {
func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath,
db *ent.Client, sessionStore *sessions.CookieStore) *Page {
if loginTexts == nil {
createLoginTexts()
}
page := &Page{
createPage: createPage,
createPage: createPage,
db: db,
sessionStore: sessionStore,
}
page.ViewRoute = NewHtmxGetRoute(page.View, LanguagePaths{
En: LoginPath,
De: LoginPathDe,
}).SetActivePath(path)
page.ViewRoute.Add(server)
// Add POST route for local login
page.localLoginRoute = NewHtmxPostRoute(page.HandleLocalLogin, LanguagePaths{
En: LocalLoginPath,
De: LocalLoginPathDe,
})
page.localLoginRoute.Add(server)
return page
}
@@ -62,6 +91,30 @@ func createLoginTexts() {
En: "Sign in with Google",
De: "Mit Google anmelden",
}),
EmailLabel: NewI18nText(map[Language]string{
En: "Email",
De: "E-Mail",
}),
PasswordLabel: NewI18nText(map[Language]string{
En: "Password",
De: "Passwort",
}),
LoginBtn: NewI18nText(map[Language]string{
En: "Sign in",
De: "Anmelden",
}),
OrSeparator: NewI18nText(map[Language]string{
En: "OR",
De: "ODER",
}),
InvalidCreds: NewI18nText(map[Language]string{
En: "Invalid email or password",
De: "Ungültige E-Mail oder Passwort",
}),
SessionSaveError: NewI18nText(map[Language]string{
En: "Failed to save session",
De: "Sitzung konnte nicht gespeichert werden",
}),
}
}
@@ -75,25 +128,95 @@ func (p *Page) getMetaData() PageWebsiteMetaData {
}
func (p *Page) View(c HtmxContext) {
content := LoginContent(c.GetLanguage())
content := p.LoginContent(c.GetLanguage(), "")
p.createPage(c, p.getMetaData(), content)
}
func LoginContent(language Language) Group {
return []Node{
func (p *Page) LoginContent(language Language, errorMsg string) Group {
nodes := []Node{
Div(Class("hero-section login-section"),
H1(Class("hero-title"), Text(loginTexts.HeroTitle.FromLang(language))),
P(Class("hero-description"), Text(loginTexts.HeroDescription.FromLang(language))),
Div(Class("login-buttons"),
A(
Href("/login/google"),
Button(
Class("btn btn-primary google-login-btn"),
Type("button"),
Text(loginTexts.GoogleLoginBtn.FromLang(language)),
),
),
}
if errorMsg != "" {
nodes = append(nodes, Div(Class("error-message"), Text(errorMsg)))
}
nodes = append(nodes,
Form(
p.localLoginRoute.GetHtmx(language),
hx.Target("body"),
Class("login-form"),
Div(Class("form-group"),
Label(For("email"), Text(loginTexts.EmailLabel.FromLang(language))),
Input(Type("email"), ID("email"), Name("email"), Required(), Class("form-control")),
),
Div(Class("form-group"),
Label(For("password"), Text(loginTexts.PasswordLabel.FromLang(language))),
Input(Type("password"), ID("password"), Name("password"), Required(), Class("form-control")),
),
Button(
Type("submit"),
Class("btn btn-primary"),
Text(loginTexts.LoginBtn.FromLang(language)),
),
),
Div(Class("separator"), Text(loginTexts.OrSeparator.FromLang(language))),
Div(Class("login-buttons"),
A(
Href("/login/google"),
Button(
Class("btn btn-primary google-login-btn"),
Type("button"),
Text(loginTexts.GoogleLoginBtn.FromLang(language)),
),
),
),
}
)
return nodes
}
func (p *Page) HandleLocalLogin(c HtmxContext) {
req := c.GetRequest()
if err := req.ParseForm(); err != nil {
content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage()))
p.createPage(c, p.getMetaData(), content)
return
}
email := req.FormValue("email")
password := req.FormValue("password")
// Find user by email
ctx := req.Context()
foundUser, err := p.db.User.Query().Where(user.EmailEQ(email)).Only(ctx)
if err != nil {
Error("could not find user ", email, "with error:", err)
content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage()))
p.createPage(c, p.getMetaData(), content)
return
}
// Verify password
if err := bcrypt.CompareHashAndPassword([]byte(foundUser.PasswordHash), []byte(password)); err != nil {
Error("could not verify password for ", email, "with error:", err)
content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage()))
p.createPage(c, p.getMetaData(), content)
return
}
// Set session
err = authentication.SaveEmailAndUserIdToSessionStore(c.GetRequest(), c.GetResponseWriter(), p.sessionStore, foundUser.Email, foundUser.ID)
if err != nil {
Error("could not save user id for ", email, "to session store:", err)
content := p.LoginContent(c.GetLanguage(), loginTexts.SessionSaveError.FromLang(c.GetLanguage()))
p.createPage(c, p.getMetaData(), content)
return
}
// Redirect to home page
http.Redirect(c.GetResponseWriter(), req, "/", http.StatusSeeOther)
}
+3 -2
View File
@@ -1,6 +1,8 @@
package routes
import (
"net/http"
. "git.gorlug.de/code/ersteller"
"git.gorlug.de/code/ersteller/authentication"
google_http "git.gorlug.de/code/ersteller/authentication/google/http"
@@ -13,7 +15,6 @@ import (
"git.gorlug.de/code/ersteller/starter/login"
"git.gorlug.de/code/ersteller/starter/page"
"git.gorlug.de/code/ersteller/starter/todos"
"net/http"
"github.com/gorilla/sessions"
. "maragu.dev/gomponents"
@@ -97,7 +98,7 @@ func CreateApi(environment env.Environment, db *ent.Client) http.Handler {
En: "Login",
De: "Anmelden",
}, loginPaths)
_ = login.NewPage(createPageFunc, server, &loginActivePath)
_ = login.NewPage(createPageFunc, server, &loginActivePath, db, sessionStore)
serverWithMiddleWare := UseMiddleware(server, LoggingMiddleware, MakeGzipHandler,
authentication.Middleware(sessionStore,
+27 -3
View File
@@ -461,6 +461,7 @@ a.disabled {
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.4);
}
/* Login section specific styles */
.login-section {
max-width: 400px;
@@ -468,12 +469,34 @@ a.disabled {
padding: 40px 30px;
}
.login-buttons {
.login-form {
margin-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
gap: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.error-message {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
color: white;
padding: 12px 20px;
border-radius: 8px;
margin: 20px 0;
text-align: center;
font-weight: 500;
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
animation: slideIn 0.3s ease;
}
/* Mobile responsive adjustments for login button */
@@ -488,3 +511,4 @@ a.disabled {
padding: 30px 20px;
}
}