Files
uPortal2/backend/internal/invite/repository.go
T

136 lines
4.1 KiB
Go
Raw Normal View History

2026-10-21 13:17:44 +03:00
package invite
import (
"context"
"database/sql"
"log/slog"
"time"
)
type Queriable interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
type Repository struct {
Db *sql.DB
q Queriable
}
func NewRepository(db *sql.DB) (*Repository, error) {
return &Repository{Db: db, q: db}, nil
}
func (r *Repository) WithTx(tx *sql.Tx) *Repository {
return &Repository{Db: r.Db, q: tx}
}
func (r *Repository) CreateInvite(ctx context.Context, token string, quota int, expiresAt time.Time) (*Invite, error) {
nowTime := time.Now().UTC()
query := "INSERT INTO invites(token, quota, uses, expires_at, created_at) VALUES(?, ?, ?, ?, ?)"
invite := &Invite{
Token: token,
Quota: quota,
Uses: 0,
ExpiresAt: expiresAt,
CreatedAt: nowTime,
}
_, err := r.q.ExecContext(ctx, query, invite.Token, invite.Quota, invite.Uses, invite.ExpiresAt, invite.CreatedAt)
if err != nil {
slog.Error("create invite", slog.String("op", "repo.CreateInvite"), slog.String("token", token), slog.Any("error", err))
return nil, ErrInternal
}
return invite, nil
}
func (r *Repository) CountInvites(ctx context.Context) (int64, error) {
query := "SELECT COUNT(*) FROM invites"
var result int64
err := r.q.QueryRowContext(ctx, query).Scan(&result)
if err != nil {
slog.Error("count invites", slog.String("op", "repo.CountInvites"), slog.Any("error", err))
return 0, ErrInternal
}
return result, nil
}
func (r *Repository) Invites(ctx context.Context, limit, offset int64) ([]*Invite, error) {
query := "SELECT token, quota, uses, expires_at, created_at FROM invites"
var args []any
if limit > 0 {
query += " LIMIT ? OFFSET ?"
args = append(args, limit, offset)
}
rows, err := r.Db.QueryContext(ctx, query, args...)
if err != nil {
slog.Error("list invites", slog.String("op", "repo.Invites"), slog.Any("error", err))
return nil, ErrInternal
}
defer rows.Close()
var invites []*Invite
for rows.Next() {
invite := &Invite{}
err := rows.Scan(&invite.Token, &invite.Quota, &invite.Uses, &invite.ExpiresAt, &invite.CreatedAt)
if err != nil {
slog.Error("scan invite", slog.String("op", "repo.Invites"), slog.Any("error", err))
return nil, ErrInternal
}
invites = append(invites, invite)
}
err = rows.Err()
if err != nil {
slog.Error("iterate invites", slog.String("op", "repo.Invites"), slog.Any("error", err))
return nil, ErrInternal
}
return invites, nil
}
func (r *Repository) InviteByToken(ctx context.Context, token string) (*Invite, error) {
query := "SELECT token, quota, uses, expires_at, created_at FROM invites WHERE token = ?"
invite := &Invite{}
err := r.q.QueryRowContext(ctx, query, token).Scan(&invite.Token, &invite.Quota, &invite.Uses, &invite.ExpiresAt, &invite.CreatedAt)
if err != nil {
return nil, ErrNotFound
}
return invite, nil
}
func (r *Repository) UpdateInvite(ctx context.Context, invite *Invite) error {
query := "UPDATE invites SET uses = ? WHERE token = ?"
_, err := r.q.ExecContext(ctx, query, invite.Uses, invite.Token)
if err != nil {
slog.Error("update invite", slog.String("op", "repo.UpdateInvite"), slog.String("token", invite.Token), slog.Any("error", err))
return ErrNotFound
}
return nil
}
func (r *Repository) DeleteInvite(ctx context.Context, token string) error {
query := "DELETE FROM invites WHERE token = ?"
_, err := r.q.ExecContext(ctx, query, token)
if err != nil {
slog.Error("delete invite", slog.String("op", "repo.DeleteInvite"), slog.String("token", token), slog.Any("error", err))
return ErrNotFound
}
return nil
}
func (r *Repository) CleanupInvites(ctx context.Context) error {
query := "DELETE FROM invites WHERE expires_at < ? OR uses >= quota"
_, err := r.q.ExecContext(ctx, query, time.Now().UTC())
if err != nil {
slog.Error("cleanup invites", slog.String("op", "repo.CleanupInvites"), slog.Any("error", err))
return ErrInternal
}
return nil
}