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 }