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/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"
"entgo.io/ent/dialect" "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "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. // Client is the client that holds all ent builders.
+3 -3
View File
@@ -6,15 +6,15 @@ import (
"context" "context"
"errors" "errors"
"fmt" "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" "reflect"
"sync" "sync"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "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. // ent aliases to avoid import conflicts in user's code.
+1 -2
View File
@@ -9,9 +9,8 @@ import (
// required by schema hooks. // required by schema hooks.
_ "git.gorlug.de/code/ersteller/starter/ent/runtime" _ "git.gorlug.de/code/ersteller/starter/ent/runtime"
"git.gorlug.de/code/ersteller/starter/ent/migrate"
"entgo.io/ent/dialect/sql/schema" "entgo.io/ent/dialect/sql/schema"
"git.gorlug.de/code/ersteller/starter/ent/migrate"
) )
type ( type (
+3 -3
View File
@@ -5,14 +5,14 @@ package ent
import ( import (
"encoding/json" "encoding/json"
"fmt" "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" "strings"
"time" "time"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "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. // GoogleAuth is the model entity for the GoogleAuth schema.
+1 -1
View File
@@ -3,11 +3,11 @@
package googleauth package googleauth
import ( import (
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"time" "time"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.
+3 -3
View File
@@ -6,13 +6,13 @@ import (
"context" "context"
"errors" "errors"
"fmt" "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" "time"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // GoogleAuthCreate is the builder for creating a GoogleAuth entity.
+2 -2
View File
@@ -4,12 +4,12 @@ package ent
import ( import (
"context" "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"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // GoogleAuthDelete is the builder for deleting a GoogleAuth entity.
+3 -3
View File
@@ -5,15 +5,15 @@ package ent
import ( import (
"context" "context"
"fmt" "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" "math"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // GoogleAuthQuery is the builder for querying GoogleAuth entities.
+4 -4
View File
@@ -6,15 +6,15 @@ import (
"context" "context"
"errors" "errors"
"fmt" "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" "time"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // GoogleAuthUpdate is the builder for updating GoogleAuth entities.
+1
View File
@@ -5,6 +5,7 @@ package hook
import ( import (
"context" "context"
"fmt" "fmt"
"git.gorlug.de/code/ersteller/starter/ent" "git.gorlug.de/code/ersteller/starter/ent"
) )
+2 -2
View File
@@ -58,8 +58,8 @@ var (
{Name: "id", Type: field.TypeInt, Increment: true}, {Name: "id", Type: field.TypeInt, Increment: true},
{Name: "created_at", Type: field.TypeTime}, {Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime},
{Name: "email", Type: field.TypeString, Default: "unknown@localhost"}, {Name: "email", Type: field.TypeString, Unique: true},
{Name: "password", Type: field.TypeString, Default: ""}, {Name: "password_hash", Type: field.TypeString, Default: ""},
} }
// UsersTable holds the schema information for the "users" table. // UsersTable holds the schema information for the "users" table.
UsersTable = &schema.Table{ UsersTable = &schema.Table{
+31 -31
View File
@@ -6,16 +6,16 @@ import (
"context" "context"
"errors" "errors"
"fmt" "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" "sync"
"time" "time"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "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 ( const (
@@ -1097,7 +1097,7 @@ type UserMutation struct {
created_at *time.Time created_at *time.Time
updated_at *time.Time updated_at *time.Time
email *string email *string
password *string password_hash *string
clearedFields map[string]struct{} clearedFields map[string]struct{}
done bool done bool
oldValue func(context.Context) (*User, error) oldValue func(context.Context) (*User, error)
@@ -1310,40 +1310,40 @@ func (m *UserMutation) ResetEmail() {
m.email = nil m.email = nil
} }
// SetPassword sets the "password" field. // SetPasswordHash sets the "password_hash" field.
func (m *UserMutation) SetPassword(s string) { func (m *UserMutation) SetPasswordHash(s string) {
m.password = &s m.password_hash = &s
} }
// Password returns the value of the "password" field in the mutation. // PasswordHash returns the value of the "password_hash" field in the mutation.
func (m *UserMutation) Password() (r string, exists bool) { func (m *UserMutation) PasswordHash() (r string, exists bool) {
v := m.password v := m.password_hash
if v == nil { if v == nil {
return return
} }
return *v, true 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. // 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. // 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) { 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 { 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) oldValue, err := m.oldValue(ctx)
if err != nil { 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. // ResetPasswordHash resets all changes to the "password_hash" field.
func (m *UserMutation) ResetPassword() { func (m *UserMutation) ResetPasswordHash() {
m.password = nil m.password_hash = nil
} }
// Where appends a list predicates to the UserMutation builder. // Where appends a list predicates to the UserMutation builder.
@@ -1390,8 +1390,8 @@ func (m *UserMutation) Fields() []string {
if m.email != nil { if m.email != nil {
fields = append(fields, user.FieldEmail) fields = append(fields, user.FieldEmail)
} }
if m.password != nil { if m.password_hash != nil {
fields = append(fields, user.FieldPassword) fields = append(fields, user.FieldPasswordHash)
} }
return fields return fields
} }
@@ -1407,8 +1407,8 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) {
return m.UpdatedAt() return m.UpdatedAt()
case user.FieldEmail: case user.FieldEmail:
return m.Email() return m.Email()
case user.FieldPassword: case user.FieldPasswordHash:
return m.Password() return m.PasswordHash()
} }
return nil, false return nil, false
} }
@@ -1424,8 +1424,8 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er
return m.OldUpdatedAt(ctx) return m.OldUpdatedAt(ctx)
case user.FieldEmail: case user.FieldEmail:
return m.OldEmail(ctx) return m.OldEmail(ctx)
case user.FieldPassword: case user.FieldPasswordHash:
return m.OldPassword(ctx) return m.OldPasswordHash(ctx)
} }
return nil, fmt.Errorf("unknown User field %s", name) 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) m.SetEmail(v)
return nil return nil
case user.FieldPassword: case user.FieldPasswordHash:
v, ok := value.(string) v, ok := value.(string)
if !ok { if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name) return fmt.Errorf("unexpected type %T for field %s", value, name)
} }
m.SetPassword(v) m.SetPasswordHash(v)
return nil return nil
} }
return fmt.Errorf("unknown User field %s", name) return fmt.Errorf("unknown User field %s", name)
@@ -1521,8 +1521,8 @@ func (m *UserMutation) ResetField(name string) error {
case user.FieldEmail: case user.FieldEmail:
m.ResetEmail() m.ResetEmail()
return nil return nil
case user.FieldPassword: case user.FieldPasswordHash:
m.ResetPassword() m.ResetPasswordHash()
return nil return nil
} }
return fmt.Errorf("unknown User field %s", name) return fmt.Errorf("unknown User field %s", name)
+6 -9
View File
@@ -3,11 +3,12 @@
package ent package ent
import ( import (
"time"
"git.gorlug.de/code/ersteller/starter/ent/googleauth" "git.gorlug.de/code/ersteller/starter/ent/googleauth"
"git.gorlug.de/code/ersteller/starter/ent/schema" "git.gorlug.de/code/ersteller/starter/ent/schema"
"git.gorlug.de/code/ersteller/starter/ent/todo" "git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user" "git.gorlug.de/code/ersteller/starter/ent/user"
"time"
) )
// The init function reads all schema descriptors with runtime code // The init function reads all schema descriptors with runtime code
@@ -67,12 +68,8 @@ func init() {
user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time)
// user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() time.Time) user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() time.Time)
// userDescEmail is the schema descriptor for email field. // userDescPasswordHash is the schema descriptor for password_hash field.
userDescEmail := userFields[0].Descriptor() userDescPasswordHash := userFields[1].Descriptor()
// user.DefaultEmail holds the default value on creation for the email field. // user.DefaultPasswordHash holds the default value on creation for the password_hash field.
user.DefaultEmail = userDescEmail.Default.(string) user.DefaultPasswordHash = userDescPasswordHash.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)
} }
+1 -1
View File
@@ -2,7 +2,7 @@
package runtime 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 ( const (
Version = "v0.14.5" // Version of ent codegen. 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 { func (User) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
field.String("email"). field.String("email").
Default("unknown@localhost"), Unique(),
field.String("password").Default(""), field.String("password_hash").
Default("").
Sensitive(),
} }
} }
+2 -2
View File
@@ -4,13 +4,13 @@ package ent
import ( import (
"fmt" "fmt"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"strings" "strings"
"time" "time"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "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. // Todo is the model entity for the Todo schema.
+1 -1
View File
@@ -3,11 +3,11 @@
package todo package todo
import ( import (
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"time" "time"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
) )
// ID filters vertices based on their ID field. // ID filters vertices based on their ID field.
+2 -2
View File
@@ -6,12 +6,12 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"git.gorlug.de/code/ersteller/starter/ent/todo"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time" "time"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // TodoCreate is the builder for creating a Todo entity.
+2 -2
View File
@@ -4,12 +4,12 @@ package ent
import ( import (
"context" "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"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // TodoDelete is the builder for deleting a Todo entity.
+3 -3
View File
@@ -5,15 +5,15 @@ package ent
import ( import (
"context" "context"
"fmt" "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" "math"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // TodoQuery is the builder for querying Todo entities.
+3 -3
View File
@@ -6,14 +6,14 @@ import (
"context" "context"
"errors" "errors"
"fmt" "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" "time"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // TodoUpdate is the builder for updating Todo entities.
+8 -9
View File
@@ -4,12 +4,12 @@ package ent
import ( import (
"fmt" "fmt"
"git.gorlug.de/code/ersteller/starter/ent/user"
"strings" "strings"
"time" "time"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"git.gorlug.de/code/ersteller/starter/ent/user"
) )
// User is the model entity for the User schema. // User is the model entity for the User schema.
@@ -23,8 +23,8 @@ type User struct {
UpdatedAt time.Time `json:"updated_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"`
// Email holds the value of the "email" field. // Email holds the value of the "email" field.
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
// Password holds the value of the "password" field. // PasswordHash holds the value of the "password_hash" field.
Password string `json:"password,omitempty"` PasswordHash string `json:"-"`
selectValues sql.SelectValues selectValues sql.SelectValues
} }
@@ -35,7 +35,7 @@ func (*User) scanValues(columns []string) ([]any, error) {
switch columns[i] { switch columns[i] {
case user.FieldID: case user.FieldID:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case user.FieldEmail, user.FieldPassword: case user.FieldEmail, user.FieldPasswordHash:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case user.FieldCreatedAt, user.FieldUpdatedAt: case user.FieldCreatedAt, user.FieldUpdatedAt:
values[i] = new(sql.NullTime) values[i] = new(sql.NullTime)
@@ -78,11 +78,11 @@ func (_m *User) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
_m.Email = value.String _m.Email = value.String
} }
case user.FieldPassword: case user.FieldPasswordHash:
if value, ok := values[i].(*sql.NullString); !ok { 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 { } else if value.Valid {
_m.Password = value.String _m.PasswordHash = value.String
} }
default: default:
_m.selectValues.Set(columns[i], values[i]) _m.selectValues.Set(columns[i], values[i])
@@ -129,8 +129,7 @@ func (_m *User) String() string {
builder.WriteString("email=") builder.WriteString("email=")
builder.WriteString(_m.Email) builder.WriteString(_m.Email)
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("password=") builder.WriteString("password_hash=<sensitive>")
builder.WriteString(_m.Password)
builder.WriteByte(')') builder.WriteByte(')')
return builder.String() return builder.String()
} }
+8 -10
View File
@@ -19,8 +19,8 @@ const (
FieldUpdatedAt = "updated_at" FieldUpdatedAt = "updated_at"
// FieldEmail holds the string denoting the email field in the database. // FieldEmail holds the string denoting the email field in the database.
FieldEmail = "email" FieldEmail = "email"
// FieldPassword holds the string denoting the password field in the database. // FieldPasswordHash holds the string denoting the password_hash field in the database.
FieldPassword = "password" FieldPasswordHash = "password_hash"
// Table holds the table name of the user in the database. // Table holds the table name of the user in the database.
Table = "users" Table = "users"
) )
@@ -31,7 +31,7 @@ var Columns = []string{
FieldCreatedAt, FieldCreatedAt,
FieldUpdatedAt, FieldUpdatedAt,
FieldEmail, FieldEmail,
FieldPassword, FieldPasswordHash,
} }
// ValidColumn reports if the column name is valid (part of the table columns). // ValidColumn reports if the column name is valid (part of the table columns).
@@ -51,10 +51,8 @@ var (
DefaultUpdatedAt func() time.Time DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time UpdateDefaultUpdatedAt func() time.Time
// DefaultEmail holds the default value on creation for the "email" field. // DefaultPasswordHash holds the default value on creation for the "password_hash" field.
DefaultEmail string DefaultPasswordHash string
// DefaultPassword holds the default value on creation for the "password" field.
DefaultPassword string
) )
// OrderOption defines the ordering options for the User queries. // 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() return sql.OrderByField(FieldEmail, opts...).ToFunc()
} }
// ByPassword orders the results by the password field. // ByPasswordHash orders the results by the password_hash field.
func ByPassword(opts ...sql.OrderTermOption) OrderOption { func ByPasswordHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPassword, opts...).ToFunc() return sql.OrderByField(FieldPasswordHash, opts...).ToFunc()
} }
+43 -43
View File
@@ -3,10 +3,10 @@
package user package user
import ( import (
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"time" "time"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
) )
// ID filters vertices based on their ID field. // 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)) return predicate.User(sql.FieldEQ(FieldEmail, v))
} }
// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ. // PasswordHash applies equality check predicate on the "password_hash" field. It's identical to PasswordHashEQ.
func Password(v string) predicate.User { func PasswordHash(v string) predicate.User {
return predicate.User(sql.FieldEQ(FieldPassword, v)) return predicate.User(sql.FieldEQ(FieldPasswordHash, v))
} }
// CreatedAtEQ applies the EQ predicate on the "created_at" field. // 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)) return predicate.User(sql.FieldContainsFold(FieldEmail, v))
} }
// PasswordEQ applies the EQ predicate on the "password" field. // PasswordHashEQ applies the EQ predicate on the "password_hash" field.
func PasswordEQ(v string) predicate.User { func PasswordHashEQ(v string) predicate.User {
return predicate.User(sql.FieldEQ(FieldPassword, v)) return predicate.User(sql.FieldEQ(FieldPasswordHash, v))
} }
// PasswordNEQ applies the NEQ predicate on the "password" field. // PasswordHashNEQ applies the NEQ predicate on the "password_hash" field.
func PasswordNEQ(v string) predicate.User { func PasswordHashNEQ(v string) predicate.User {
return predicate.User(sql.FieldNEQ(FieldPassword, v)) return predicate.User(sql.FieldNEQ(FieldPasswordHash, v))
} }
// PasswordIn applies the In predicate on the "password" field. // PasswordHashIn applies the In predicate on the "password_hash" field.
func PasswordIn(vs ...string) predicate.User { func PasswordHashIn(vs ...string) predicate.User {
return predicate.User(sql.FieldIn(FieldPassword, vs...)) return predicate.User(sql.FieldIn(FieldPasswordHash, vs...))
} }
// PasswordNotIn applies the NotIn predicate on the "password" field. // PasswordHashNotIn applies the NotIn predicate on the "password_hash" field.
func PasswordNotIn(vs ...string) predicate.User { func PasswordHashNotIn(vs ...string) predicate.User {
return predicate.User(sql.FieldNotIn(FieldPassword, vs...)) return predicate.User(sql.FieldNotIn(FieldPasswordHash, vs...))
} }
// PasswordGT applies the GT predicate on the "password" field. // PasswordHashGT applies the GT predicate on the "password_hash" field.
func PasswordGT(v string) predicate.User { func PasswordHashGT(v string) predicate.User {
return predicate.User(sql.FieldGT(FieldPassword, v)) return predicate.User(sql.FieldGT(FieldPasswordHash, v))
} }
// PasswordGTE applies the GTE predicate on the "password" field. // PasswordHashGTE applies the GTE predicate on the "password_hash" field.
func PasswordGTE(v string) predicate.User { func PasswordHashGTE(v string) predicate.User {
return predicate.User(sql.FieldGTE(FieldPassword, v)) return predicate.User(sql.FieldGTE(FieldPasswordHash, v))
} }
// PasswordLT applies the LT predicate on the "password" field. // PasswordHashLT applies the LT predicate on the "password_hash" field.
func PasswordLT(v string) predicate.User { func PasswordHashLT(v string) predicate.User {
return predicate.User(sql.FieldLT(FieldPassword, v)) return predicate.User(sql.FieldLT(FieldPasswordHash, v))
} }
// PasswordLTE applies the LTE predicate on the "password" field. // PasswordHashLTE applies the LTE predicate on the "password_hash" field.
func PasswordLTE(v string) predicate.User { func PasswordHashLTE(v string) predicate.User {
return predicate.User(sql.FieldLTE(FieldPassword, v)) return predicate.User(sql.FieldLTE(FieldPasswordHash, v))
} }
// PasswordContains applies the Contains predicate on the "password" field. // PasswordHashContains applies the Contains predicate on the "password_hash" field.
func PasswordContains(v string) predicate.User { func PasswordHashContains(v string) predicate.User {
return predicate.User(sql.FieldContains(FieldPassword, v)) return predicate.User(sql.FieldContains(FieldPasswordHash, v))
} }
// PasswordHasPrefix applies the HasPrefix predicate on the "password" field. // PasswordHashHasPrefix applies the HasPrefix predicate on the "password_hash" field.
func PasswordHasPrefix(v string) predicate.User { func PasswordHashHasPrefix(v string) predicate.User {
return predicate.User(sql.FieldHasPrefix(FieldPassword, v)) return predicate.User(sql.FieldHasPrefix(FieldPasswordHash, v))
} }
// PasswordHasSuffix applies the HasSuffix predicate on the "password" field. // PasswordHashHasSuffix applies the HasSuffix predicate on the "password_hash" field.
func PasswordHasSuffix(v string) predicate.User { func PasswordHashHasSuffix(v string) predicate.User {
return predicate.User(sql.FieldHasSuffix(FieldPassword, v)) return predicate.User(sql.FieldHasSuffix(FieldPasswordHash, v))
} }
// PasswordEqualFold applies the EqualFold predicate on the "password" field. // PasswordHashEqualFold applies the EqualFold predicate on the "password_hash" field.
func PasswordEqualFold(v string) predicate.User { func PasswordHashEqualFold(v string) predicate.User {
return predicate.User(sql.FieldEqualFold(FieldPassword, v)) return predicate.User(sql.FieldEqualFold(FieldPasswordHash, v))
} }
// PasswordContainsFold applies the ContainsFold predicate on the "password" field. // PasswordHashContainsFold applies the ContainsFold predicate on the "password_hash" field.
func PasswordContainsFold(v string) predicate.User { func PasswordHashContainsFold(v string) predicate.User {
return predicate.User(sql.FieldContainsFold(FieldPassword, v)) return predicate.User(sql.FieldContainsFold(FieldPasswordHash, v))
} }
// And groups predicates with the AND operator between them. // And groups predicates with the AND operator between them.
+15 -27
View File
@@ -6,11 +6,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time" "time"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"git.gorlug.de/code/ersteller/starter/ent/user"
) )
// UserCreate is the builder for creating a User entity. // UserCreate is the builder for creating a User entity.
@@ -54,24 +54,16 @@ func (_c *UserCreate) SetEmail(v string) *UserCreate {
return _c return _c
} }
// SetNillableEmail sets the "email" field if the given value is not nil. // SetPasswordHash sets the "password_hash" field.
func (_c *UserCreate) SetNillableEmail(v *string) *UserCreate { func (_c *UserCreate) SetPasswordHash(v string) *UserCreate {
if v != nil { _c.mutation.SetPasswordHash(v)
_c.SetEmail(*v)
}
return _c return _c
} }
// SetPassword sets the "password" field. // SetNillablePasswordHash sets the "password_hash" field if the given value is not nil.
func (_c *UserCreate) SetPassword(v string) *UserCreate { func (_c *UserCreate) SetNillablePasswordHash(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 {
if v != nil { if v != nil {
_c.SetPassword(*v) _c.SetPasswordHash(*v)
} }
return _c return _c
} }
@@ -119,13 +111,9 @@ func (_c *UserCreate) defaults() {
v := user.DefaultUpdatedAt() v := user.DefaultUpdatedAt()
_c.mutation.SetUpdatedAt(v) _c.mutation.SetUpdatedAt(v)
} }
if _, ok := _c.mutation.Email(); !ok { if _, ok := _c.mutation.PasswordHash(); !ok {
v := user.DefaultEmail v := user.DefaultPasswordHash
_c.mutation.SetEmail(v) _c.mutation.SetPasswordHash(v)
}
if _, ok := _c.mutation.Password(); !ok {
v := user.DefaultPassword
_c.mutation.SetPassword(v)
} }
} }
@@ -140,8 +128,8 @@ func (_c *UserCreate) check() error {
if _, ok := _c.mutation.Email(); !ok { if _, ok := _c.mutation.Email(); !ok {
return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "User.email"`)} return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "User.email"`)}
} }
if _, ok := _c.mutation.Password(); !ok { if _, ok := _c.mutation.PasswordHash(); !ok {
return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "User.password"`)} return &ValidationError{Name: "password_hash", err: errors.New(`ent: missing required field "User.password_hash"`)}
} }
return nil return nil
} }
@@ -181,9 +169,9 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
_spec.SetField(user.FieldEmail, field.TypeString, value) _spec.SetField(user.FieldEmail, field.TypeString, value)
_node.Email = value _node.Email = value
} }
if value, ok := _c.mutation.Password(); ok { if value, ok := _c.mutation.PasswordHash(); ok {
_spec.SetField(user.FieldPassword, field.TypeString, value) _spec.SetField(user.FieldPasswordHash, field.TypeString, value)
_node.Password = value _node.PasswordHash = value
} }
return _node, _spec return _node, _spec
} }
+2 -2
View File
@@ -4,12 +4,12 @@ package ent
import ( import (
"context" "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"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // UserDelete is the builder for deleting a User entity.
+2 -2
View File
@@ -5,14 +5,14 @@ package ent
import ( import (
"context" "context"
"fmt" "fmt"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
"math" "math"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // UserQuery is the builder for querying User entities.
+18 -18
View File
@@ -6,13 +6,13 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"git.gorlug.de/code/ersteller/starter/ent/predicate"
"git.gorlug.de/code/ersteller/starter/ent/user"
"time" "time"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "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. // UserUpdate is the builder for updating User entities.
@@ -48,16 +48,16 @@ func (_u *UserUpdate) SetNillableEmail(v *string) *UserUpdate {
return _u return _u
} }
// SetPassword sets the "password" field. // SetPasswordHash sets the "password_hash" field.
func (_u *UserUpdate) SetPassword(v string) *UserUpdate { func (_u *UserUpdate) SetPasswordHash(v string) *UserUpdate {
_u.mutation.SetPassword(v) _u.mutation.SetPasswordHash(v)
return _u return _u
} }
// SetNillablePassword sets the "password" field if the given value is not nil. // SetNillablePasswordHash sets the "password_hash" field if the given value is not nil.
func (_u *UserUpdate) SetNillablePassword(v *string) *UserUpdate { func (_u *UserUpdate) SetNillablePasswordHash(v *string) *UserUpdate {
if v != nil { if v != nil {
_u.SetPassword(*v) _u.SetPasswordHash(*v)
} }
return _u return _u
} }
@@ -118,8 +118,8 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.Email(); ok { if value, ok := _u.mutation.Email(); ok {
_spec.SetField(user.FieldEmail, field.TypeString, value) _spec.SetField(user.FieldEmail, field.TypeString, value)
} }
if value, ok := _u.mutation.Password(); ok { if value, ok := _u.mutation.PasswordHash(); ok {
_spec.SetField(user.FieldPassword, field.TypeString, value) _spec.SetField(user.FieldPasswordHash, field.TypeString, value)
} }
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok { if _, ok := err.(*sqlgraph.NotFoundError); ok {
@@ -161,16 +161,16 @@ func (_u *UserUpdateOne) SetNillableEmail(v *string) *UserUpdateOne {
return _u return _u
} }
// SetPassword sets the "password" field. // SetPasswordHash sets the "password_hash" field.
func (_u *UserUpdateOne) SetPassword(v string) *UserUpdateOne { func (_u *UserUpdateOne) SetPasswordHash(v string) *UserUpdateOne {
_u.mutation.SetPassword(v) _u.mutation.SetPasswordHash(v)
return _u return _u
} }
// SetNillablePassword sets the "password" field if the given value is not nil. // SetNillablePasswordHash sets the "password_hash" field if the given value is not nil.
func (_u *UserUpdateOne) SetNillablePassword(v *string) *UserUpdateOne { func (_u *UserUpdateOne) SetNillablePasswordHash(v *string) *UserUpdateOne {
if v != nil { if v != nil {
_u.SetPassword(*v) _u.SetPasswordHash(*v)
} }
return _u return _u
} }
@@ -261,8 +261,8 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
if value, ok := _u.mutation.Email(); ok { if value, ok := _u.mutation.Email(); ok {
_spec.SetField(user.FieldEmail, field.TypeString, value) _spec.SetField(user.FieldEmail, field.TypeString, value)
} }
if value, ok := _u.mutation.Password(); ok { if value, ok := _u.mutation.PasswordHash(); ok {
_spec.SetField(user.FieldPassword, field.TypeString, value) _spec.SetField(user.FieldPasswordHash, field.TypeString, value)
} }
_node = &User{config: _u.config} _node = &User{config: _u.config}
_spec.Assign = _node.assignValues _spec.Assign = _node.assignValues
+14 -12
View File
@@ -7,20 +7,22 @@ require (
git.gorlug.de/code/ersteller v0.0.0-20251115110439-18d05566e2fc git.gorlug.de/code/ersteller v0.0.0-20251115110439-18d05566e2fc
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
github.com/joho/godotenv v1.5.1 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 v1.2.0
maragu.dev/gomponents-htmx v0.6.1
) )
require ( require (
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect 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/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect github.com/go-openapi/inflect v0.19.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect github.com/gorilla/mux v1.6.2 // indirect
github.com/gorilla/securecookie v1.1.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-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // 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/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty v1.14.4 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect github.com/zclconf/go-cty-yaml v1.1.0 // indirect
golang.org/x/crypto v0.42.0 // indirect golang.org/x/mod v0.29.0 // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/net v0.47.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.39.0 // indirect
golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.31.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/tools v0.38.0 // indirect
golang.org/x/tools v0.36.0 // indirect
maragu.dev/gomponents-htmx v0.6.1 // 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 h1:E0wvcUXTkgyN4wy4LGtNzMNGMytJN8afmIWXJVMi4cc=
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= 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.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= 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 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U= entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
git.gorlug.de/code/ersteller v0.0.0-20251115110439-18d05566e2fc h1:oin+Mm/Z9FaR0To40MDGrFsEdK7s7FDaC9vImeyvMdg= 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-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 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 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 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 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/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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 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 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 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= 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.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 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/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+144 -21
View File
@@ -1,7 +1,15 @@
package login package login
import ( import (
"net/http"
. "git.gorlug.de/code/ersteller" . "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"
. "maragu.dev/gomponents/html" . "maragu.dev/gomponents/html"
@@ -9,34 +17,55 @@ import (
const LoginPath = "/login" const LoginPath = "/login"
const LoginPathDe = "/anmelden" const LoginPathDe = "/anmelden"
const LocalLoginPath = "/login/local"
const LocalLoginPathDe = "/anmelden/lokal"
var loginTexts *LoginTexts var loginTexts *LoginTexts
type LoginTexts struct { type LoginTexts struct {
PageTitle I18nText PageTitle I18nText
PageDescription I18nText PageDescription I18nText
HeroTitle I18nText HeroTitle I18nText
HeroDescription I18nText HeroDescription I18nText
GoogleLoginBtn I18nText GoogleLoginBtn I18nText
EmailLabel I18nText
PasswordLabel I18nText
LoginBtn I18nText
OrSeparator I18nText
InvalidCreds I18nText
SessionSaveError I18nText
} }
type Page struct { type Page struct {
createPage CreateHtmxPageFunc createPage CreateHtmxPageFunc
ViewRoute HtmxRoute 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 { if loginTexts == nil {
createLoginTexts() createLoginTexts()
} }
page := &Page{ page := &Page{
createPage: createPage, createPage: createPage,
db: db,
sessionStore: sessionStore,
} }
page.ViewRoute = NewHtmxGetRoute(page.View, LanguagePaths{ page.ViewRoute = NewHtmxGetRoute(page.View, LanguagePaths{
En: LoginPath, En: LoginPath,
De: LoginPathDe, De: LoginPathDe,
}).SetActivePath(path) }).SetActivePath(path)
page.ViewRoute.Add(server) 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 return page
} }
@@ -62,6 +91,30 @@ func createLoginTexts() {
En: "Sign in with Google", En: "Sign in with Google",
De: "Mit Google anmelden", 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) { func (p *Page) View(c HtmxContext) {
content := LoginContent(c.GetLanguage()) content := p.LoginContent(c.GetLanguage(), "")
p.createPage(c, p.getMetaData(), content) p.createPage(c, p.getMetaData(), content)
} }
func LoginContent(language Language) Group { func (p *Page) LoginContent(language Language, errorMsg string) Group {
return []Node{ nodes := []Node{
Div(Class("hero-section login-section"), Div(Class("hero-section login-section"),
H1(Class("hero-title"), Text(loginTexts.HeroTitle.FromLang(language))), H1(Class("hero-title"), Text(loginTexts.HeroTitle.FromLang(language))),
P(Class("hero-description"), Text(loginTexts.HeroDescription.FromLang(language))), P(Class("hero-description"), Text(loginTexts.HeroDescription.FromLang(language))),
Div(Class("login-buttons"), ),
A( }
Href("/login/google"),
Button( if errorMsg != "" {
Class("btn btn-primary google-login-btn"), nodes = append(nodes, Div(Class("error-message"), Text(errorMsg)))
Type("button"), }
Text(loginTexts.GoogleLoginBtn.FromLang(language)),
), 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 package routes
import ( import (
"net/http"
. "git.gorlug.de/code/ersteller" . "git.gorlug.de/code/ersteller"
"git.gorlug.de/code/ersteller/authentication" "git.gorlug.de/code/ersteller/authentication"
google_http "git.gorlug.de/code/ersteller/authentication/google/http" 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/login"
"git.gorlug.de/code/ersteller/starter/page" "git.gorlug.de/code/ersteller/starter/page"
"git.gorlug.de/code/ersteller/starter/todos" "git.gorlug.de/code/ersteller/starter/todos"
"net/http"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
. "maragu.dev/gomponents" . "maragu.dev/gomponents"
@@ -97,7 +98,7 @@ func CreateApi(environment env.Environment, db *ent.Client) http.Handler {
En: "Login", En: "Login",
De: "Anmelden", De: "Anmelden",
}, loginPaths) }, loginPaths)
_ = login.NewPage(createPageFunc, server, &loginActivePath) _ = login.NewPage(createPageFunc, server, &loginActivePath, db, sessionStore)
serverWithMiddleWare := UseMiddleware(server, LoggingMiddleware, MakeGzipHandler, serverWithMiddleWare := UseMiddleware(server, LoggingMiddleware, MakeGzipHandler,
authentication.Middleware(sessionStore, 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); box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.4);
} }
/* Login section specific styles */ /* Login section specific styles */
.login-section { .login-section {
max-width: 400px; max-width: 400px;
@@ -468,12 +469,34 @@ a.disabled {
padding: 40px 30px; padding: 40px 30px;
} }
.login-buttons { .login-form {
margin-top: 30px; margin-top: 30px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; gap: 20px;
gap: 16px; }
.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 */ /* Mobile responsive adjustments for login button */
@@ -488,3 +511,4 @@ a.disabled {
padding: 30px 20px; padding: 30px 20px;
} }
} }