v0.33.0 62a09f5

session_repo.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close