246 lines
5.9 KiB
Go
246 lines
5.9 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"code.capytal.cc/capytal/comicverse/model"
|
|
"code.capytal.cc/loreddev/x/tinyssert"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Token struct {
|
|
baseRepostiory
|
|
}
|
|
|
|
// Must be initiated after [User]
|
|
func NewToken(ctx context.Context, db *sql.DB, log *slog.Logger, assert tinyssert.Assertions) (*Token, error) {
|
|
b := newBaseRepostiory(ctx, db, log, assert)
|
|
|
|
tx, err := db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `
|
|
CREATE TABLE IF NOT EXISTS tokens (
|
|
id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
|
|
PRIMARY KEY(id, user_id),
|
|
FOREIGN KEY(user_id)
|
|
REFERENCES users (id)
|
|
ON DELETE CASCADE
|
|
ON UPDATE RESTRICT
|
|
)`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return nil, errors.Join(errors.New("unable to create project tables"), err)
|
|
}
|
|
|
|
return &Token{baseRepostiory: b}, nil
|
|
}
|
|
|
|
func (repo Token) Create(token model.Token) error {
|
|
repo.assert.NotNil(repo.db)
|
|
repo.assert.NotNil(repo.ctx)
|
|
repo.assert.NotNil(repo.log)
|
|
|
|
if err := token.Validate(); err != nil {
|
|
return errors.Join(ErrInvalidInput, err)
|
|
}
|
|
|
|
tx, err := repo.db.BeginTx(repo.ctx, nil)
|
|
if err != nil {
|
|
return errors.Join(ErrDatabaseConn, err)
|
|
}
|
|
|
|
q := `
|
|
INSERT INTO tokens (id, user_id, created_at, expires_at)
|
|
VALUES (:id, :user_id, :created_at, :expires_at)
|
|
`
|
|
|
|
log := repo.log.With(slog.String("id", token.ID.String()),
|
|
slog.String("user_id", token.UserID.String()),
|
|
slog.String("expires", token.DateExpires.Format(dateFormat)),
|
|
slog.String("query", q))
|
|
log.DebugContext(repo.ctx, "Inserting new user token")
|
|
|
|
// TODO: Check rows affected
|
|
_, err = tx.ExecContext(repo.ctx, q,
|
|
sql.Named("id", token.ID),
|
|
sql.Named("user_id", token.UserID),
|
|
sql.Named("created_at", token.DateCreated.Format(dateFormat)),
|
|
sql.Named("expired_at", token.DateExpires.Format(dateFormat)),
|
|
)
|
|
if err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to insert token", slog.String("error", err.Error()))
|
|
return errors.Join(ErrExecuteQuery, err)
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
|
|
return errors.Join(ErrCommitQuery, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (repo Token) Get(tokenID, userID uuid.UUID) (model.Token, error) {
|
|
repo.assert.NotNil(repo.db)
|
|
repo.assert.NotNil(repo.ctx)
|
|
repo.assert.NotNil(repo.log)
|
|
|
|
q := `
|
|
SELECT (id, user_id, created_at, expired_at) FROM tokens
|
|
WHERE id = :id
|
|
AND user_id = :user_id
|
|
`
|
|
|
|
log := repo.log.With(slog.String("id", tokenID.String()),
|
|
slog.String("user_id", userID.String()),
|
|
slog.String("query", q))
|
|
log.DebugContext(repo.ctx, "Getting token")
|
|
|
|
row := repo.db.QueryRowContext(repo.ctx, q,
|
|
sql.Named("id", tokenID),
|
|
sql.Named("user_id", userID),
|
|
)
|
|
|
|
token, err := repo.scan(row)
|
|
if err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to scan token", slog.String("error", err.Error()))
|
|
return model.Token{}, err
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func (repo Token) GetByUserID(userID uuid.UUID) (tokens []model.Token, err error) {
|
|
repo.assert.NotNil(repo.db)
|
|
repo.assert.NotNil(repo.ctx)
|
|
repo.assert.NotNil(repo.log)
|
|
|
|
q := `
|
|
SELECT (id, user_id, created_at, expired_at) FROM tokens
|
|
WHERE user_id = :user_id
|
|
`
|
|
|
|
log := repo.log.With(
|
|
slog.String("user_id", userID.String()),
|
|
slog.String("query", q),
|
|
)
|
|
log.DebugContext(repo.ctx, "Getting users tokens")
|
|
|
|
rows, err := repo.db.QueryContext(repo.ctx, q,
|
|
sql.Named("user_id", userID),
|
|
)
|
|
|
|
defer func() {
|
|
err = rows.Close()
|
|
if err != nil {
|
|
err = errors.Join(ErrCloseConn, err)
|
|
}
|
|
}()
|
|
|
|
if err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to get user tokens", slog.String("error", err.Error()))
|
|
return []model.Token{}, errors.Join(ErrExecuteQuery, err)
|
|
}
|
|
|
|
tokens = []model.Token{}
|
|
for rows.Next() {
|
|
t, err := repo.scan(rows)
|
|
if err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to scan token", slog.String("error", err.Error()))
|
|
return []model.Token{}, err
|
|
}
|
|
|
|
tokens = append(tokens, t)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to scan token rows", slog.String("error", err.Error()))
|
|
return []model.Token{}, errors.Join(ErrExecuteQuery, err)
|
|
}
|
|
|
|
return tokens, err
|
|
}
|
|
|
|
func (repo Token) scan(row scan) (model.Token, error) {
|
|
repo.assert.NotNil(repo.ctx)
|
|
|
|
var token model.Token
|
|
var createdStr, expiresStr string
|
|
|
|
err := row.Scan(&token.ID, &token.UserID, &createdStr, &expiresStr)
|
|
if err != nil {
|
|
return model.Token{}, errors.Join(ErrExecuteQuery, err)
|
|
}
|
|
|
|
dateCreated, err := time.Parse(dateFormat, createdStr)
|
|
if err != nil {
|
|
return model.Token{}, errors.Join(ErrInvalidOutput, err)
|
|
}
|
|
|
|
dateExpires, err := time.Parse(dateFormat, createdStr)
|
|
if err != nil {
|
|
return model.Token{}, errors.Join(ErrInvalidOutput, err)
|
|
}
|
|
|
|
token.DateCreated = dateCreated
|
|
token.DateExpires = dateExpires
|
|
|
|
if err := token.Validate(); err != nil {
|
|
return model.Token{}, errors.Join(ErrInvalidOutput, err)
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func (repo Token) Delete(token, user uuid.UUID) error {
|
|
repo.assert.NotNil(repo.db)
|
|
repo.assert.NotNil(repo.ctx)
|
|
repo.assert.NotNil(repo.log)
|
|
|
|
tx, err := repo.db.BeginTx(repo.ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
q := `
|
|
DELETE FROM tokens
|
|
WHERE id = :id
|
|
AND user_id = :user_id
|
|
`
|
|
|
|
log := repo.log.With(slog.String("id", token.String()),
|
|
slog.String("user_id", user.String()),
|
|
slog.String("query", q))
|
|
log.DebugContext(repo.ctx, "Deleting token")
|
|
|
|
_, err = tx.ExecContext(repo.ctx, q,
|
|
sql.Named("id", token),
|
|
sql.Named("user_id", user),
|
|
)
|
|
if err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to delete token", slog.String("error", err.Error()))
|
|
return errors.Join(ErrExecuteQuery, err)
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
log.ErrorContext(repo.ctx, "Failed to commit transaction", slog.String("error", err.Error()))
|
|
return errors.Join(ErrCommitQuery, err)
|
|
}
|
|
|
|
return nil
|
|
}
|