148 lines
4.9 KiB
Go
148 lines
4.9 KiB
Go
package auth
|
|||
|
|
|
||
|
|
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) CreateSession(ctx context.Context, token string, userId int64, address string, device string, expiresAt time.Time, isLong bool) (*Session, error) {
|
||
|
|
nowTime := time.Now().UTC()
|
||
|
|
query := "INSERT INTO sessions(token, user_id, address, device, created_at, expires_at, is_long) VALUES(?, ?, ?, ?, ?, ?, ?)"
|
||
|
|
session := &Session{
|
||
|
|
UserId: userId,
|
||
|
|
Token: token,
|
||
|
|
Address: address,
|
||
|
|
Device: device,
|
||
|
|
CreatedAt: nowTime,
|
||
|
|
ExpiresAt: expiresAt,
|
||
|
|
IsLong: isLong,
|
||
|
|
}
|
||
|
|
_, err := r.q.ExecContext(ctx, query, session.Token, session.UserId, session.Address, session.Device, session.CreatedAt, session.ExpiresAt, session.IsLong)
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("create session", slog.String("op", "repo.CreateSession"), slog.Int64("user_id", userId), slog.Any("error", err))
|
||
|
|
return nil, ErrInternal
|
||
|
|
}
|
||
|
|
|
||
|
|
return session, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Repository) CountSessions(ctx context.Context, userId int64) (int64, error) {
|
||
|
|
query := "SELECT COUNT(*) FROM sessions"
|
||
|
|
|
||
|
|
var result int64
|
||
|
|
err := r.q.QueryRowContext(ctx, query).Scan(&result)
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("count sessions", slog.String("op", "repo.CountSessions"), slog.Int64("user_id", userId), slog.Any("error", err))
|
||
|
|
return 0, ErrInternal
|
||
|
|
}
|
||
|
|
return result, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Repository) Sessions(ctx context.Context, userId, limit, offset int64) ([]*Session, error) {
|
||
|
|
query := "SELECT token, user_id, address, device, created_at, expires_at, is_long FROM sessions WHERE user_id = ?"
|
||
|
|
var args []any
|
||
|
|
|
||
|
|
args = append(args, userId)
|
||
|
|
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 sessions", slog.String("op", "repo.Sessions"), slog.Int64("user_id", userId), slog.Any("error", err))
|
||
|
|
return nil, ErrInternal
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
var sessions []*Session
|
||
|
|
for rows.Next() {
|
||
|
|
session := &Session{}
|
||
|
|
err := rows.Scan(&session.Token, &session.UserId, &session.Address, &session.Device, &session.CreatedAt, &session.ExpiresAt, &session.IsLong)
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("scan session", slog.String("op", "repo.Sessions"), slog.Int64("user_id", userId), slog.Any("error", err))
|
||
|
|
return nil, ErrInternal
|
||
|
|
}
|
||
|
|
sessions = append(sessions, session)
|
||
|
|
}
|
||
|
|
|
||
|
|
err = rows.Err()
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("iterate sessions", slog.String("op", "repo.Sessions"), slog.Int64("user_id", userId), slog.Any("error", err))
|
||
|
|
return nil, ErrInternal
|
||
|
|
}
|
||
|
|
|
||
|
|
return sessions, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Repository) SessionByToken(ctx context.Context, token string) (*Session, error) {
|
||
|
|
query := "SELECT token, user_id, address, device, created_at, expires_at, is_long FROM sessions WHERE token = ?"
|
||
|
|
session := &Session{}
|
||
|
|
err := r.q.QueryRowContext(ctx, query, token).Scan(&session.Token, &session.UserId, &session.Address, &session.Device, &session.CreatedAt, &session.ExpiresAt, &session.IsLong)
|
||
|
|
if err != nil {
|
||
|
|
return nil, ErrNotFound
|
||
|
|
}
|
||
|
|
return session, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Repository) UpdateSession(ctx context.Context, session *Session) error {
|
||
|
|
query := "UPDATE sessions SET expires_at = ? WHERE token = ?"
|
||
|
|
_, err := r.q.ExecContext(ctx, query, session.ExpiresAt, session.Token)
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("update session", slog.String("op", "repo.UpdateSession"), slog.Int64("user_id", session.UserId), slog.Any("error", err))
|
||
|
|
return ErrNotFound
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Repository) DeleteSessions(ctx context.Context, userId int64) error {
|
||
|
|
query := "DELETE FROM sessions WHERE user_id = ?"
|
||
|
|
_, err := r.q.ExecContext(ctx, query, userId)
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("delete sessions", slog.String("op", "repo.DeleteSessions"), slog.Int64("user_id", userId), slog.Any("error", err))
|
||
|
|
return ErrNotFound
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Repository) DeleteSession(ctx context.Context, token string) error {
|
||
|
|
query := "DELETE FROM sessions WHERE token = ?"
|
||
|
|
_, err := r.q.ExecContext(ctx, query, token)
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("delete session", slog.String("op", "repo.DeleteSession"), slog.Any("error", err))
|
||
|
|
return ErrNotFound
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Repository) CleanupSessions(ctx context.Context) error {
|
||
|
|
query := "DELETE FROM sessions WHERE expires_at < ?"
|
||
|
|
_, err := r.q.ExecContext(ctx, query, time.Now().UTC())
|
||
|
|
if err != nil {
|
||
|
|
slog.Error("cleanup sessions", slog.String("op", "repo.CleanupSessions"), slog.Any("error", err))
|
||
|
|
return ErrInternal
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|