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 var processID sql.NullInt64
36 if session.ProcessID != nil {
37 processID = sql.NullInt64{Int64: int64(*session.ProcessID), Valid: true}
38 }
39
40 return r.queries.InsertSession(ctx, sqlc.InsertSessionParams{
41 ID: string(session.ID),
42 Type: string(session.Type),
43 StartedAt: session.StartedAt.UTC(),
44 EndedAt: endedAt,
45 ProcessID: processID,
46 })
47}
48
49func (r *sessionRepo) FindByID(ctx context.Context, id entity.SessionID) (*entity.Session, error) {
50 row, err := r.queries.GetSessionByID(ctx, string(id))
51 if err != nil {
52 if errors.Is(err, sql.ErrNoRows) {
53 return nil, nil
54 }
55 return nil, err
56 }
57 return sessionFromRow(row), nil
58}
59
60func (r *sessionRepo) GetActive(ctx context.Context) (*entity.Session, error) {
61 row, err := r.queries.GetActiveBrowserSession(ctx)
62 if err != nil {
63 if errors.Is(err, sql.ErrNoRows) {
64 return nil, nil
65 }
66 return nil, err
67 }
68 return sessionFromRow(row), nil
69}
70
71func (r *sessionRepo) GetRecent(ctx context.Context, limit int) ([]*entity.Session, error) {
72 if limit == 0 {
73 limit = 20
74 }
75 if limit < 0 {
76 limit = -1
77 }
78 rows, err := r.queries.GetRecentSessions(ctx, int64(limit))
79 if err != nil {
80 return nil, err
81 }
82
83 sessions := make([]*entity.Session, len(rows))
84 for i := range rows {
85 sessions[i] = sessionFromRow(rows[i])
86 }
87 return sessions, nil
88}
89
90func (r *sessionRepo) MarkEnded(ctx context.Context, id entity.SessionID, endedAt time.Time) error {
91 endedAt = endedAt.UTC()
92 return r.queries.MarkSessionEnded(ctx, sqlc.MarkSessionEndedParams{
93 EndedAt: sql.NullTime{Time: endedAt, Valid: true},
94 ID: string(id),
95 })
96}
97
98func (r *sessionRepo) Delete(ctx context.Context, id entity.SessionID) error {
99 log := logging.FromContext(ctx)
100 log.Debug().Str("session_id", string(id)).Msg("deleting session")
101 return r.queries.DeleteSession(ctx, string(id))
102}
103
104func (r *sessionRepo) DeleteOldestExited(ctx context.Context, keepCount int) (int64, error) {
105 log := logging.FromContext(ctx)
106 if keepCount < 0 {
107 keepCount = 0
108 }
109 deleted, err := r.queries.DeleteOldestExitedSessions(ctx, int64(keepCount))
110 if err != nil {
111 return 0, err
112 }
113 if deleted > 0 {
114 log.Debug().Int64("deleted", deleted).Int("keep_count", keepCount).Msg("deleted oldest exited sessions")
115 }
116 return deleted, nil
117}
118
119func (r *sessionRepo) DeleteExitedBefore(ctx context.Context, cutoff time.Time) (int64, error) {
120 log := logging.FromContext(ctx)
121 deleted, err := r.queries.DeleteExitedSessionsBefore(ctx, sql.NullTime{Time: cutoff.UTC(), Valid: true})
122 if err != nil {
123 return 0, err
124 }
125 if deleted > 0 {
126 log.Debug().Int64("deleted", deleted).Time("cutoff", cutoff).Msg("deleted exited sessions before cutoff")
127 }
128 return deleted, nil
129}
130
131func sessionFromRow(row sqlc.Session) *entity.Session {
132 var endedAt *time.Time
133 if row.EndedAt.Valid {
134 t := row.EndedAt.Time.UTC()
135 endedAt = &t
136 }
137
138 var processID *int
139 if row.ProcessID.Valid {
140 pid := int(row.ProcessID.Int64)
141 processID = &pid
142 }
143
144 return &entity.Session{
145 ID: entity.SessionID(row.ID),
146 Type: entity.SessionType(row.Type),
147 StartedAt: row.StartedAt.UTC(),
148 EndedAt: endedAt,
149 ProcessID: processID,
150 }
151}