detached de0b075

feat(domain): add session purge entity and repository methods

commit
de0b075a54b685d9a17074ece46251ce211918fa
author
brice · · 2025-12-25 10:16 UTC
parent
b375737
signature
unsigned
7 files · +103 -0
M internal/domain/entity/purge.go +6 -0
@@ -30,3 +30,9 @@ type PurgeResult struct {
3030 Success bool
3131 Error error
3232 }
33+
34+// SessionPurgeItem represents an inactive session in the purge selection UI.
35+type SessionPurgeItem struct {
36+ Info SessionInfo
37+ Selected bool
38+}
@@ -30,3 +30,9 @@ type PurgeResult struct {
30 Success bool30 Success bool
31 Error error31 Error error
32 }32 }
33+
34+// SessionPurgeItem represents an inactive session in the purge selection UI.
35+type SessionPurgeItem struct {
36+ Info SessionInfo
37+ Selected bool
38+}
M internal/domain/repository/mocks/mocks.go +60 -0
@@ -3335,6 +3335,66 @@ func (_c *MockSessionStateRepository_GetSnapshot_Call) RunAndReturn(run func(ctx
33353335 return _c
33363336 }
33373337
3338+// GetTotalSnapshotsSize provides a mock function for the type MockSessionStateRepository
3339+func (_mock *MockSessionStateRepository) GetTotalSnapshotsSize(ctx context.Context) (int64, error) {
3340+ ret := _mock.Called(ctx)
3341+
3342+ if len(ret) == 0 {
3343+ panic("no return value specified for GetTotalSnapshotsSize")
3344+ }
3345+
3346+ var r0 int64
3347+ var r1 error
3348+ if returnFunc, ok := ret.Get(0).(func(context.Context) (int64, error)); ok {
3349+ return returnFunc(ctx)
3350+ }
3351+ if returnFunc, ok := ret.Get(0).(func(context.Context) int64); ok {
3352+ r0 = returnFunc(ctx)
3353+ } else {
3354+ r0 = ret.Get(0).(int64)
3355+ }
3356+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
3357+ r1 = returnFunc(ctx)
3358+ } else {
3359+ r1 = ret.Error(1)
3360+ }
3361+ return r0, r1
3362+}
3363+
3364+// MockSessionStateRepository_GetTotalSnapshotsSize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTotalSnapshotsSize'
3365+type MockSessionStateRepository_GetTotalSnapshotsSize_Call struct {
3366+ *mock.Call
3367+}
3368+
3369+// GetTotalSnapshotsSize is a helper method to define mock.On call
3370+// - ctx context.Context
3371+func (_e *MockSessionStateRepository_Expecter) GetTotalSnapshotsSize(ctx interface{}) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3372+ return &MockSessionStateRepository_GetTotalSnapshotsSize_Call{Call: _e.mock.On("GetTotalSnapshotsSize", ctx)}
3373+}
3374+
3375+func (_c *MockSessionStateRepository_GetTotalSnapshotsSize_Call) Run(run func(ctx context.Context)) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3376+ _c.Call.Run(func(args mock.Arguments) {
3377+ var arg0 context.Context
3378+ if args[0] != nil {
3379+ arg0 = args[0].(context.Context)
3380+ }
3381+ run(
3382+ arg0,
3383+ )
3384+ })
3385+ return _c
3386+}
3387+
3388+func (_c *MockSessionStateRepository_GetTotalSnapshotsSize_Call) Return(n int64, err error) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3389+ _c.Call.Return(n, err)
3390+ return _c
3391+}
3392+
3393+func (_c *MockSessionStateRepository_GetTotalSnapshotsSize_Call) RunAndReturn(run func(ctx context.Context) (int64, error)) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3394+ _c.Call.Return(run)
3395+ return _c
3396+}
3397+
33383398 // SaveSnapshot provides a mock function for the type MockSessionStateRepository
33393399 func (_mock *MockSessionStateRepository) SaveSnapshot(ctx context.Context, state *entity.SessionState) error {
33403400 ret := _mock.Called(ctx, state)
@@ -3335,6 +3335,66 @@ func (_c *MockSessionStateRepository_GetSnapshot_Call) RunAndReturn(run func(ctx
3335 return _c3335 return _c
3336 }3336 }
3337 3337
3338+// GetTotalSnapshotsSize provides a mock function for the type MockSessionStateRepository
3339+func (_mock *MockSessionStateRepository) GetTotalSnapshotsSize(ctx context.Context) (int64, error) {
3340+ ret := _mock.Called(ctx)
3341+
3342+ if len(ret) == 0 {
3343+ panic("no return value specified for GetTotalSnapshotsSize")
3344+ }
3345+
3346+ var r0 int64
3347+ var r1 error
3348+ if returnFunc, ok := ret.Get(0).(func(context.Context) (int64, error)); ok {
3349+ return returnFunc(ctx)
3350+ }
3351+ if returnFunc, ok := ret.Get(0).(func(context.Context) int64); ok {
3352+ r0 = returnFunc(ctx)
3353+ } else {
3354+ r0 = ret.Get(0).(int64)
3355+ }
3356+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
3357+ r1 = returnFunc(ctx)
3358+ } else {
3359+ r1 = ret.Error(1)
3360+ }
3361+ return r0, r1
3362+}
3363+
3364+// MockSessionStateRepository_GetTotalSnapshotsSize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTotalSnapshotsSize'
3365+type MockSessionStateRepository_GetTotalSnapshotsSize_Call struct {
3366+ *mock.Call
3367+}
3368+
3369+// GetTotalSnapshotsSize is a helper method to define mock.On call
3370+// - ctx context.Context
3371+func (_e *MockSessionStateRepository_Expecter) GetTotalSnapshotsSize(ctx interface{}) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3372+ return &MockSessionStateRepository_GetTotalSnapshotsSize_Call{Call: _e.mock.On("GetTotalSnapshotsSize", ctx)}
3373+}
3374+
3375+func (_c *MockSessionStateRepository_GetTotalSnapshotsSize_Call) Run(run func(ctx context.Context)) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3376+ _c.Call.Run(func(args mock.Arguments) {
3377+ var arg0 context.Context
3378+ if args[0] != nil {
3379+ arg0 = args[0].(context.Context)
3380+ }
3381+ run(
3382+ arg0,
3383+ )
3384+ })
3385+ return _c
3386+}
3387+
3388+func (_c *MockSessionStateRepository_GetTotalSnapshotsSize_Call) Return(n int64, err error) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3389+ _c.Call.Return(n, err)
3390+ return _c
3391+}
3392+
3393+func (_c *MockSessionStateRepository_GetTotalSnapshotsSize_Call) RunAndReturn(run func(ctx context.Context) (int64, error)) *MockSessionStateRepository_GetTotalSnapshotsSize_Call {
3394+ _c.Call.Return(run)
3395+ return _c
3396+}
3397+
3338 // SaveSnapshot provides a mock function for the type MockSessionStateRepository3398 // SaveSnapshot provides a mock function for the type MockSessionStateRepository
3339 func (_mock *MockSessionStateRepository) SaveSnapshot(ctx context.Context, state *entity.SessionState) error {3399 func (_mock *MockSessionStateRepository) SaveSnapshot(ctx context.Context, state *entity.SessionState) error {
3340 ret := _mock.Called(ctx, state)3400 ret := _mock.Called(ctx, state)
M internal/domain/repository/session_state.go +3 -0
@@ -20,4 +20,7 @@ type SessionStateRepository interface {
2020 // GetAllSnapshots returns all snapshots with summary info.
2121 // Used by ListSessionsUseCase.
2222 GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error)
23+
24+ // GetTotalSnapshotsSize returns the total size of all session snapshots in bytes.
25+ GetTotalSnapshotsSize(ctx context.Context) (int64, error)
2326 }
@@ -20,4 +20,7 @@ type SessionStateRepository interface {
20 // GetAllSnapshots returns all snapshots with summary info.20 // GetAllSnapshots returns all snapshots with summary info.
21 // Used by ListSessionsUseCase.21 // Used by ListSessionsUseCase.
22 GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error)22 GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error)
23+
24+ // GetTotalSnapshotsSize returns the total size of all session snapshots in bytes.
25+ GetTotalSnapshotsSize(ctx context.Context) (int64, error)
23 }26 }
M internal/infrastructure/persistence/sqlite/queries/session_states.sql +4 -0
@@ -30,3 +30,7 @@ LEFT JOIN session_states ss ON s.id = ss.session_id
3030 WHERE s.type = 'browser'
3131 ORDER BY s.started_at DESC
3232 LIMIT ?;
33+
34+-- name: GetTotalSessionStatesSize :one
35+SELECT COALESCE(SUM(LENGTH(state_json)), 0) as total_size
36+FROM session_states;
@@ -30,3 +30,7 @@ LEFT JOIN session_states ss ON s.id = ss.session_id
30 WHERE s.type = 'browser'30 WHERE s.type = 'browser'
31 ORDER BY s.started_at DESC31 ORDER BY s.started_at DESC
32 LIMIT ?;32 LIMIT ?;
33+
34+-- name: GetTotalSessionStatesSize :one
35+SELECT COALESCE(SUM(LENGTH(state_json)), 0) as total_size
36+FROM session_states;
M internal/infrastructure/persistence/sqlite/session_state_repo.go +17 -0
@@ -99,3 +99,20 @@ func (r *sessionStateRepo) GetAllSnapshots(ctx context.Context) ([]*entity.Sessi
9999
100100 return states, nil
101101 }
102+
103+// GetTotalSnapshotsSize returns the total size of all session snapshots in bytes.
104+func (r *sessionStateRepo) GetTotalSnapshotsSize(ctx context.Context) (int64, error) {
105+ result, err := r.queries.GetTotalSessionStatesSize(ctx)
106+ if err != nil {
107+ return 0, err
108+ }
109+ // SQLite returns int64 for SUM, but COALESCE wraps it as interface{}
110+ switch v := result.(type) {
111+ case int64:
112+ return v, nil
113+ case float64:
114+ return int64(v), nil
115+ default:
116+ return 0, nil
117+ }
118+}
@@ -99,3 +99,20 @@ func (r *sessionStateRepo) GetAllSnapshots(ctx context.Context) ([]*entity.Sessi
99 99
100 return states, nil100 return states, nil
101 }101 }
102+
103+// GetTotalSnapshotsSize returns the total size of all session snapshots in bytes.
104+func (r *sessionStateRepo) GetTotalSnapshotsSize(ctx context.Context) (int64, error) {
105+ result, err := r.queries.GetTotalSessionStatesSize(ctx)
106+ if err != nil {
107+ return 0, err
108+ }
109+ // SQLite returns int64 for SUM, but COALESCE wraps it as interface{}
110+ switch v := result.(type) {
111+ case int64:
112+ return v, nil
113+ case float64:
114+ return int64(v), nil
115+ default:
116+ return 0, nil
117+ }
118+}
M internal/infrastructure/persistence/sqlite/sqlc/querier.go +1 -0
@@ -52,6 +52,7 @@ type Querier interface {
5252 GetTagByID(ctx context.Context, id int64) (FavoriteTag, error)
5353 GetTagByName(ctx context.Context, name string) (FavoriteTag, error)
5454 GetTagsForFavorite(ctx context.Context, favoriteID int64) ([]FavoriteTag, error)
55+ GetTotalSessionStatesSize(ctx context.Context) (interface{}, error)
5556 GetZoomLevel(ctx context.Context, domain string) (ZoomLevel, error)
5657 IncrementVisitCount(ctx context.Context, url string) error
5758 InsertSession(ctx context.Context, arg InsertSessionParams) error
@@ -52,6 +52,7 @@ type Querier interface {
52 GetTagByID(ctx context.Context, id int64) (FavoriteTag, error)52 GetTagByID(ctx context.Context, id int64) (FavoriteTag, error)
53 GetTagByName(ctx context.Context, name string) (FavoriteTag, error)53 GetTagByName(ctx context.Context, name string) (FavoriteTag, error)
54 GetTagsForFavorite(ctx context.Context, favoriteID int64) ([]FavoriteTag, error)54 GetTagsForFavorite(ctx context.Context, favoriteID int64) ([]FavoriteTag, error)
55+ GetTotalSessionStatesSize(ctx context.Context) (interface{}, error)
55 GetZoomLevel(ctx context.Context, domain string) (ZoomLevel, error)56 GetZoomLevel(ctx context.Context, domain string) (ZoomLevel, error)
56 IncrementVisitCount(ctx context.Context, url string) error57 IncrementVisitCount(ctx context.Context, url string) error
57 InsertSession(ctx context.Context, arg InsertSessionParams) error58 InsertSession(ctx context.Context, arg InsertSessionParams) error
M internal/infrastructure/persistence/sqlite/sqlc/session_states.sql.go +12 -0
@@ -130,6 +130,18 @@ func (q *Queries) GetSessionsWithState(ctx context.Context, limit int64) ([]GetS
130130 return items, nil
131131 }
132132
133+const GetTotalSessionStatesSize = `-- name: GetTotalSessionStatesSize :one
134+SELECT COALESCE(SUM(LENGTH(state_json)), 0) as total_size
135+FROM session_states
136+`
137+
138+func (q *Queries) GetTotalSessionStatesSize(ctx context.Context) (interface{}, error) {
139+ row := q.db.QueryRowContext(ctx, GetTotalSessionStatesSize)
140+ var total_size interface{}
141+ err := row.Scan(&total_size)
142+ return total_size, err
143+}
144+
133145 const UpsertSessionState = `-- name: UpsertSessionState :exec
134146 INSERT INTO session_states (session_id, state_json, version, tab_count, pane_count, updated_at)
135147 VALUES (?, ?, ?, ?, ?, ?)
@@ -130,6 +130,18 @@ func (q *Queries) GetSessionsWithState(ctx context.Context, limit int64) ([]GetS
130 return items, nil130 return items, nil
131 }131 }
132 132
133+const GetTotalSessionStatesSize = `-- name: GetTotalSessionStatesSize :one
134+SELECT COALESCE(SUM(LENGTH(state_json)), 0) as total_size
135+FROM session_states
136+`
137+
138+func (q *Queries) GetTotalSessionStatesSize(ctx context.Context) (interface{}, error) {
139+ row := q.db.QueryRowContext(ctx, GetTotalSessionStatesSize)
140+ var total_size interface{}
141+ err := row.Scan(&total_size)
142+ return total_size, err
143+}
144+
133 const UpsertSessionState = `-- name: UpsertSessionState :exec145 const UpsertSessionState = `-- name: UpsertSessionState :exec
134 INSERT INTO session_states (session_id, state_json, version, tab_count, pane_count, updated_at)146 INSERT INTO session_states (session_id, state_json, version, tab_count, pane_count, updated_at)
135 VALUES (?, ?, ?, ?, ?, ?)147 VALUES (?, ?, ?, ?, ?, ?)

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