Files
comicverse/repository/user.go

212 lines
5.1 KiB
Go
Raw Normal View History

2025-05-30 18:03:56 -03:00
package repository
import (
"context"
"database/sql"
"encoding/base64"
"errors"
"log/slog"
"time"
2025-10-13 15:26:31 -03:00
"code.capytal.cc/capytal/comicverse/model"
"code.capytal.cc/loreddev/x/tinyssert"
2025-06-10 14:56:03 -03:00
"github.com/google/uuid"
2025-05-30 18:03:56 -03:00
)
type User struct {
baseRepostiory
2025-05-30 18:03:56 -03:00
}
func NewUser(
2025-05-30 18:03:56 -03:00
ctx context.Context,
db *sql.DB,
2025-05-30 18:03:56 -03:00
logger *slog.Logger,
assert tinyssert.Assertions,
) (*User, error) {
assert.NotNil(ctx)
assert.NotNil(db)
2025-05-30 18:03:56 -03:00
assert.NotNil(logger)
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS users (
2025-06-10 14:46:09 -03:00
id TEXT NOT NULL PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
2025-05-30 18:03:56 -03:00
password_hash TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
2025-05-30 18:03:56 -03:00
)`)
if err != nil {
return nil, err
}
b := newBaseRepostiory(ctx, db, logger, assert)
return &User{
baseRepostiory: b,
2025-05-30 18:03:56 -03:00
}, nil
}
func (repo *User) Create(u model.User) (model.User, error) {
repo.assert.NotNil(repo.db)
repo.assert.NotNil(repo.log)
repo.assert.NotNil(repo.ctx)
if err := u.Validate(); err != nil {
return model.User{}, errors.Join(ErrInvalidInput, err)
}
tx, err := repo.db.BeginTx(repo.ctx, nil)
2025-05-30 18:03:56 -03:00
if err != nil {
return model.User{}, errors.Join(ErrDatabaseConn, err)
2025-05-30 18:03:56 -03:00
}
q := `
2025-06-10 14:46:09 -03:00
INSERT INTO users (id, username, password_hash, created_at, updated_at)
VALUES (:id, :username, :password_hash, :created_at, :updated_at)
2025-05-30 18:03:56 -03:00
`
2025-06-10 14:46:09 -03:00
log := repo.log.With(
slog.String("id", u.ID.String()),
slog.String("username", u.Username),
slog.String("query", q))
log.DebugContext(repo.ctx, "Inserting new user")
2025-05-30 18:03:56 -03:00
t := time.Now()
passwd := base64.URLEncoding.EncodeToString(u.Password)
_, err = tx.ExecContext(repo.ctx, q,
2025-06-10 14:46:09 -03:00
sql.Named("id", u.ID),
2025-05-30 18:03:56 -03:00
sql.Named("username", u.Username),
sql.Named("password_hash", passwd),
sql.Named("created_at", t.Format(dateFormat)),
sql.Named("updated_at", t.Format(dateFormat)))
2025-05-30 18:03:56 -03:00
if err != nil {
log.ErrorContext(repo.ctx, "Failed to create user", slog.String("error", err.Error()))
return model.User{}, errors.Join(ErrExecuteQuery, err)
2025-05-30 18:03:56 -03:00
}
if err := tx.Commit(); err != nil {
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
return model.User{}, errors.Join(ErrCommitQuery, err)
2025-05-30 18:03:56 -03:00
}
return u, nil
}
func (repo *User) GetByID(id uuid.UUID) (model.User, error) {
2025-06-10 14:56:03 -03:00
repo.assert.NotNil(repo.db)
repo.assert.NotNil(repo.log)
repo.assert.NotNil(repo.ctx)
q := `
SELECT id, username, password_hash, created_at, updated_at FROM users
WHERE id = :id
`
log := repo.log.With(
slog.String("id", id.String()),
slog.String("query", q))
log.DebugContext(repo.ctx, "Querying user")
row := repo.db.QueryRowContext(repo.ctx, q, sql.Named("username", id))
user, err := repo.scan(row)
if err != nil {
log.ErrorContext(repo.ctx, "Failed to query user", slog.String("error", err.Error()))
return model.User{}, err
}
return user, nil
}
func (repo *User) GetByUsername(username string) (model.User, error) {
repo.assert.NotNil(repo.db)
repo.assert.NotNil(repo.log)
repo.assert.NotNil(repo.ctx)
2025-05-30 18:03:56 -03:00
q := `
2025-06-10 14:46:09 -03:00
SELECT id, username, password_hash, created_at, updated_at FROM users
2025-05-30 18:03:56 -03:00
WHERE username = :username
`
2025-06-10 14:46:09 -03:00
log := repo.log.With(
slog.String("username", username),
slog.String("query", q))
log.DebugContext(repo.ctx, "Querying user")
2025-05-30 18:03:56 -03:00
row := repo.db.QueryRowContext(repo.ctx, q, sql.Named("username", username))
2025-05-30 18:03:56 -03:00
user, err := repo.scan(row)
if err != nil {
log.ErrorContext(repo.ctx, "Failed to query user", slog.String("error", err.Error()))
return model.User{}, err
}
return user, nil
}
func (repo *User) scan(row scan) (model.User, error) {
2025-06-10 14:46:09 -03:00
var user model.User
var password_hashStr, createdStr, updatedStr string
err := row.Scan(&user.ID, &user.Username, &password_hashStr, &createdStr, &updatedStr)
if err != nil {
return model.User{}, errors.Join(ErrExecuteQuery, err)
2025-05-30 18:03:56 -03:00
}
2025-06-10 14:46:09 -03:00
passwd, err := base64.URLEncoding.DecodeString(password_hashStr)
2025-05-30 18:03:56 -03:00
if err != nil {
return model.User{}, errors.Join(ErrInvalidOutput, err)
2025-05-30 18:03:56 -03:00
}
created, err := time.Parse(dateFormat, createdStr)
2025-05-30 18:03:56 -03:00
if err != nil {
return model.User{}, errors.Join(ErrInvalidOutput, err)
2025-05-30 18:03:56 -03:00
}
updated, err := time.Parse(dateFormat, updatedStr)
2025-05-30 18:03:56 -03:00
if err != nil {
return model.User{}, errors.Join(ErrInvalidOutput, err)
2025-05-30 18:03:56 -03:00
}
user.Password = passwd
user.DateCreated = created
user.DateUpdated = updated
if err := user.Validate(); err != nil {
return model.User{}, errors.Join(ErrInvalidOutput, err)
}
return user, nil
2025-05-30 18:03:56 -03:00
}
func (repo *User) DeleteByID(id uuid.UUID) error {
repo.assert.NotNil(repo.db)
repo.assert.NotNil(repo.log)
repo.assert.NotNil(repo.ctx)
tx, err := repo.db.BeginTx(repo.ctx, nil)
2025-05-30 18:03:56 -03:00
if err != nil {
return err
}
q := `
2025-06-10 14:46:09 -03:00
DELETE FROM users WHERE id = :id
2025-05-30 18:03:56 -03:00
`
log := repo.log.With(slog.String("id", id.String()), slog.String("query", q))
log.DebugContext(repo.ctx, "Deleting user")
2025-05-30 18:03:56 -03:00
_, err = tx.ExecContext(repo.ctx, q, sql.Named("id", id))
2025-05-30 18:03:56 -03:00
if err != nil {
log.ErrorContext(repo.ctx, "Failed to delete user", slog.String("error", err.Error()))
return errors.Join(ErrExecuteQuery, err)
2025-05-30 18:03:56 -03:00
}
if err := tx.Commit(); err != nil {
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
return errors.Join(ErrCommitQuery, err)
2025-05-30 18:03:56 -03:00
}
return nil
}