package repository import ( "context" "database/sql" "errors" "tailly_back_v2/internal/domain" "time" ) var ( ErrSessionNotFound = errors.New("session not found") ) type SessionRepository interface { Save(ctx context.Context, session *domain.Session) error GetByID(ctx context.Context, id int) (*domain.Session, error) GetActiveByUser(ctx context.Context, userID int) ([]*domain.Session, error) Terminate(ctx context.Context, id int) error TerminateAllForUser(ctx context.Context, userID int) error } type sessionRepository struct { db *sql.DB } func NewSessionRepository(db *sql.DB) SessionRepository { return &sessionRepository{db: db} } func (r *sessionRepository) Save(ctx context.Context, session *domain.Session) error { query := ` INSERT INTO sessions ( user_id, device_id, started_at ) VALUES ($1, $2, $3) RETURNING id ` err := r.db.QueryRowContext(ctx, query, session.UserID, session.DeviceID, session.StartedAt, ).Scan(&session.ID) return err } func (r *sessionRepository) GetByID(ctx context.Context, id int) (*domain.Session, error) { query := ` SELECT id, user_id, device_id, started_at, ended_at FROM sessions WHERE id = $1 ` session := &domain.Session{} var endedAt sql.NullTime err := r.db.QueryRowContext(ctx, query, id).Scan( &session.ID, &session.UserID, &session.DeviceID, &session.StartedAt, &endedAt, ) if err == sql.ErrNoRows { return nil, ErrSessionNotFound } if endedAt.Valid { session.EndedAt = endedAt.Time } return session, err } func (r *sessionRepository) GetActiveByUser(ctx context.Context, userID int) ([]*domain.Session, error) { query := ` SELECT s.id, s.user_id, s.device_id, s.started_at, s.ended_at, d.name as device_name, d.ip_address, d.user_agent FROM sessions s JOIN devices d ON s.device_id = d.id WHERE s.user_id = $1 AND s.ended_at IS NULL ORDER BY s.started_at DESC ` rows, err := r.db.QueryContext(ctx, query, userID) if err != nil { return nil, err } defer rows.Close() var sessions []*domain.Session for rows.Next() { var session domain.Session var endedAt sql.NullTime var device domain.Device err := rows.Scan( &session.ID, &session.UserID, &session.DeviceID, &session.StartedAt, &endedAt, &device.Name, &device.IPAddress, &device.UserAgent, ) if err != nil { return nil, err } if endedAt.Valid { session.EndedAt = endedAt.Time } session.Device = &device sessions = append(sessions, &session) } return sessions, nil } func (r *sessionRepository) Terminate(ctx context.Context, id int) error { query := ` UPDATE sessions SET ended_at = $1 WHERE id = $2 ` _, err := r.db.ExecContext(ctx, query, time.Now(), id) return err } func (r *sessionRepository) TerminateAllForUser(ctx context.Context, userID int) error { query := ` UPDATE sessions SET ended_at = $1 WHERE user_id = $2 AND ended_at IS NULL ` _, err := r.db.ExecContext(ctx, query, time.Now(), userID) return err }