1package sqlite
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "time"
8
9 "github.com/bnema/dumber/internal/domain/entity"
10 "github.com/bnema/dumber/internal/domain/repository"
11 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite/sqlc"
12 "github.com/bnema/dumber/internal/logging"
13)
14
15type sessionRepo struct {
16 queries *sqlc.Queries
17}
18
19func NewSessionRepository(db *sql.DB) repository.SessionRepository {
20 return &sessionRepo{queries: sqlc.New(db)}
21}
22
23func (r *sessionRepo) Save(ctx context.Context, session *entity.Session) error {
24 log := logging.FromContext(ctx)
25 if err := session.Validate(); err != nil {
26 return err
27 }
28
29 log.Debug().Str("session", string(session.ID)).Str("type", string(session.Type)).Msg("saving session")
30
31 var endedAt sql.NullTime
32 if session.EndedAt != nil {
33 endedAt = sql.NullTime{Time: session.EndedAt.UTC(), Valid: true}
34 }
35
36 return r.queries.InsertSession(ctx, sqlc.InsertSessionParams{
37 ID: string(session.ID),
38 Type: string(session.Type),
39 StartedAt: session.StartedAt.UTC(),
40 EndedAt: endedAt,
41 })
42}
43
44func (r *sessionRepo) FindByID(ctx context.Context, id entity.SessionID) (*entity.Session, error) {
45 row, err := r.queries.GetSessionByID(ctx, string(id))
46 if err != nil {
47 if errors.Is(err, sql.ErrNoRows) {
48 return nil, nil
49 }
50 return nil, err
51 }
52 return sessionFromRow(row), nil
53}
54
55func (r *sessionRepo) GetActive(ctx context.Context) (*entity.Session, error) {
56 row, err := r.queries.GetActiveBrowserSession(ctx)
57 if err != nil {
58 if errors.Is(err, sql.ErrNoRows) {
59 return nil, nil
60 }
61 return nil, err
62 }
63 return sessionFromRow(row), nil
64}
65
66func (r *sessionRepo) GetRecent(ctx context.Context, limit int) ([]*entity.Session, error) {
67 if limit <= 0 {
68 limit = 20
69 }
70 rows, err := r.queries.GetRecentSessions(ctx, int64(limit))
71 if err != nil {
72 return nil, err
73 }
74
75 sessions := make([]*entity.Session, len(rows))
76 for i := range rows {
77 sessions[i] = sessionFromRow(rows[i])
78 }
79 return sessions, nil
80}
81
82func (r *sessionRepo) MarkEnded(ctx context.Context, id entity.SessionID, endedAt time.Time) error {
83 endedAt = endedAt.UTC()
84 return r.queries.MarkSessionEnded(ctx, sqlc.MarkSessionEndedParams{
85 EndedAt: sql.NullTime{Time: endedAt, Valid: true},
86 ID: string(id),
87 })
88}
89
90func (r *sessionRepo) Delete(ctx context.Context, id entity.SessionID) error {
91 log := logging.FromContext(ctx)
92 log.Debug().Str("session_id", string(id)).Msg("deleting session")
93 return r.queries.DeleteSession(ctx, string(id))
94}
95
96func (r *sessionRepo) DeleteOldestExited(ctx context.Context, keepCount int) (int64, error) {
97 log := logging.FromContext(ctx)
98 if keepCount < 0 {
99 keepCount = 0
100 }
101 deleted, err := r.queries.DeleteOldestExitedSessions(ctx, int64(keepCount))
102 if err != nil {
103 return 0, err
104 }
105 if deleted > 0 {
106 log.Debug().Int64("deleted", deleted).Int("keep_count", keepCount).Msg("deleted oldest exited sessions")
107 }
108 return deleted, nil
109}
110
111func (r *sessionRepo) DeleteExitedBefore(ctx context.Context, cutoff time.Time) (int64, error) {
112 log := logging.FromContext(ctx)
113 deleted, err := r.queries.DeleteExitedSessionsBefore(ctx, sql.NullTime{Time: cutoff.UTC(), Valid: true})
114 if err != nil {
115 return 0, err
116 }
117 if deleted > 0 {
118 log.Debug().Int64("deleted", deleted).Time("cutoff", cutoff).Msg("deleted exited sessions before cutoff")
119 }
120 return deleted, nil
121}
122
123func sessionFromRow(row sqlc.Session) *entity.Session {
124 var endedAt *time.Time
125 if row.EndedAt.Valid {
126 t := row.EndedAt.Time.UTC()
127 endedAt = &t
128 }
129
130 return &entity.Session{
131 ID: entity.SessionID(row.ID),
132 Type: entity.SessionType(row.Type),
133 StartedAt: row.StartedAt.UTC(),
134 EndedAt: endedAt,
135 }
136}