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=
+129 -6
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,6 +17,8 @@ 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
@@ -18,25 +28,44 @@ type LoginTexts struct {
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,15 +128,42 @@ 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))),
),
}
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"), Div(Class("login-buttons"),
A( A(
Href("/login/google"), Href("/login/google"),
@@ -94,6 +174,49 @@ func LoginContent(language Language) Group {
), ),
), ),
), ),
), )
}
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;
} }
} }