package models import ( "context" "github.com/pkg/errors" "github.com/uptrace/bun" "golang.org/x/crypto/bcrypt" ) type UserBun struct { bun.BaseModel `bun:"table:users,alias:u"` ID int `bun:"id,pk,autoincrement"` // Integer ID (index primary key) Username string `bun:"username,unique"` // Username (unique) PasswordHash string `bun:"password_hash,nullzero"` // Bcrypt hashed password (not exported in JSON) CreatedAt int64 `bun:"created_at"` // Epoch timestamp when the user was added to the database Bio string `bun:"bio"` // Short byline set by the user } func (user *UserBun) GetID() int { return user.ID } // Uses bcrypt to set the users password_hash from the given password func (user *UserBun) SetPassword(ctx context.Context, tx bun.Tx, password string) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return errors.Wrap(err, "bcrypt.GenerateFromPassword") } newPassword := string(hashedPassword) _, err = tx.NewUpdate(). Model(user). Set("password_hash = ?", newPassword). Where("id = ?", user.ID). Exec(ctx) if err != nil { return errors.Wrap(err, "tx.Update") } return nil } // Uses bcrypt to check if the given password matches the users password_hash func (user *UserBun) CheckPassword(ctx context.Context, tx bun.Tx, password string) error { var hashedPassword string err := tx.NewSelect(). Table("users"). Column("password_hash"). Where("id = ?", user.ID). Limit(1). Scan(ctx, &hashedPassword) if err != nil { return errors.Wrap(err, "tx.Select") } err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) if err != nil { return errors.Wrap(err, "Username or password incorrect") } return nil } // Change the user's username func (user *UserBun) ChangeUsername(ctx context.Context, tx bun.Tx, newUsername string) error { _, err := tx.NewUpdate(). Model(user). Set("username = ?", newUsername). Where("id = ?", user.ID). Exec(ctx) if err != nil { return errors.Wrap(err, "tx.Update") } user.Username = newUsername return nil } // Change the user's bio func (user *UserBun) ChangeBio(ctx context.Context, tx bun.Tx, newBio string) error { _, err := tx.NewUpdate(). Model(user). Set("bio = ?", newBio). Where("id = ?", user.ID). Exec(ctx) if err != nil { return errors.Wrap(err, "tx.Update") } user.Bio = newBio return nil } // CreateUser creates a new user with the given username and password func CreateUser(ctx context.Context, tx bun.Tx, username, password string) (*UserBun, error) { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, errors.Wrap(err, "bcrypt.GenerateFromPassword") } user := &UserBun{ Username: username, PasswordHash: string(hashedPassword), CreatedAt: 0, // You may want to set this to time.Now().Unix() Bio: "", } _, err = tx.NewInsert(). Model(user). Exec(ctx) if err != nil { return nil, errors.Wrap(err, "tx.Insert") } return user, nil } // GetUserByID queries the database for a user matching the given ID // Returns nil, nil if no user is found func GetUserByID(ctx context.Context, tx bun.Tx, id int) (*UserBun, error) { user := new(UserBun) err := tx.NewSelect(). Model(user). Where("id = ?", id). Limit(1). Scan(ctx) if err != nil { if err.Error() == "sql: no rows in result set" { return nil, nil } return nil, errors.Wrap(err, "tx.Select") } return user, nil } // GetUserByUsername queries the database for a user matching the given username // Returns nil, nil if no user is found func GetUserByUsername(ctx context.Context, tx bun.Tx, username string) (*UserBun, error) { user := new(UserBun) err := tx.NewSelect(). Model(user). Where("username = ?", username). Limit(1). Scan(ctx) if err != nil { if err.Error() == "sql: no rows in result set" { return nil, nil } return nil, errors.Wrap(err, "tx.Select") } return user, nil } // IsUsernameUnique checks if the given username is unique (not already taken) // Returns true if the username is available, false if it's taken func IsUsernameUnique(ctx context.Context, tx bun.Tx, username string) (bool, error) { count, err := tx.NewSelect(). Model((*UserBun)(nil)). Where("username = ?", username). Count(ctx) if err != nil { return false, errors.Wrap(err, "tx.Count") } return count == 0, nil }