Add authentication, styling needs to be fixed

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