v0.27.2 5960dd8

lazy_repos.go RawHistoryFolder

1// Package sqlite provides SQLite implementations of domain repositories.
2//
3// # Lazy Repository Infrastructure
4//
5// This file contains lazy-loading repository wrappers that defer database initialization
6// until first access. While currently unused (the application uses eager initialization
7// via OpenDatabase in RunParallelDBWebKit), this infrastructure is kept for potential
8// future optimization scenarios:
9//
10//   - CLI commands that may not need database access
11//   - Further startup latency reduction by deferring DB init past first paint
12//   - Testing with mock DatabaseProvider implementations
13//
14// The lazy wrappers implement the same repository interfaces as their eager counterparts,
15// making them drop-in replacements when lazy initialization becomes beneficial.
16package sqlite
17
18import (
19	"context"
20	"database/sql"
21	"fmt"
22	"sync"
23	"time"
24
25	"github.com/bnema/dumber/internal/application/port"
26	"github.com/bnema/dumber/internal/domain/entity"
27	"github.com/bnema/dumber/internal/domain/repository"
28)
29
30// LazyHistoryRepository wraps a history repository with lazy database initialization.
31type LazyHistoryRepository struct {
32	provider port.DatabaseProvider
33	repo     repository.HistoryRepository
34	once     sync.Once
35	initErr  error
36}
37
38// NewLazyHistoryRepository creates a lazy-loading history repository.
39func NewLazyHistoryRepository(provider port.DatabaseProvider) repository.HistoryRepository {
40	return &LazyHistoryRepository{provider: provider}
41}
42
43// LazyPermissionRepository wraps a permission repository with lazy database initialization.
44type LazyPermissionRepository struct {
45	provider port.DatabaseProvider
46	repo     port.PermissionRepository
47	once     sync.Once
48	initErr  error
49}
50
51// NewLazyPermissionRepository creates a lazy-loading permission repository.
52func NewLazyPermissionRepository(provider port.DatabaseProvider) port.PermissionRepository {
53	return &LazyPermissionRepository{provider: provider}
54}
55
56func (r *LazyPermissionRepository) init(ctx context.Context) error {
57	r.once.Do(func() {
58		db, err := r.provider.DB(ctx)
59		if err != nil {
60			r.initErr = err
61			return
62		}
63		r.repo = NewPermissionRepository(db)
64	})
65	return r.initErr
66}
67
68func (r *LazyPermissionRepository) Get(
69	ctx context.Context,
70	origin string,
71	permType entity.PermissionType,
72) (*entity.PermissionRecord, error) {
73	if err := r.init(ctx); err != nil {
74		return nil, err
75	}
76	return r.repo.Get(ctx, origin, permType)
77}
78
79func (r *LazyPermissionRepository) Set(ctx context.Context, record *entity.PermissionRecord) error {
80	if err := r.init(ctx); err != nil {
81		return err
82	}
83	return r.repo.Set(ctx, record)
84}
85
86func (r *LazyPermissionRepository) Delete(ctx context.Context, origin string, permType entity.PermissionType) error {
87	if err := r.init(ctx); err != nil {
88		return err
89	}
90	return r.repo.Delete(ctx, origin, permType)
91}
92
93func (r *LazyPermissionRepository) GetAll(ctx context.Context, origin string) ([]*entity.PermissionRecord, error) {
94	if err := r.init(ctx); err != nil {
95		return nil, err
96	}
97	return r.repo.GetAll(ctx, origin)
98}
99
100func (r *LazyHistoryRepository) init(ctx context.Context) error {
101	r.once.Do(func() {
102		db, err := r.provider.DB(ctx)
103		if err != nil {
104			r.initErr = err
105			return
106		}
107		r.repo = NewHistoryRepository(db)
108	})
109	return r.initErr
110}
111
112func (r *LazyHistoryRepository) Save(ctx context.Context, entry *entity.HistoryEntry) error {
113	if err := r.init(ctx); err != nil {
114		return err
115	}
116	return r.repo.Save(ctx, entry)
117}
118
119func (r *LazyHistoryRepository) FindByURL(ctx context.Context, url string) (*entity.HistoryEntry, error) {
120	if err := r.init(ctx); err != nil {
121		return nil, err
122	}
123	return r.repo.FindByURL(ctx, url)
124}
125
126func (r *LazyHistoryRepository) Search(ctx context.Context, query string, limit int) ([]entity.HistoryMatch, error) {
127	if err := r.init(ctx); err != nil {
128		return nil, err
129	}
130	return r.repo.Search(ctx, query, limit)
131}
132
133func (r *LazyHistoryRepository) GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) {
134	if err := r.init(ctx); err != nil {
135		return nil, err
136	}
137	return r.repo.GetRecent(ctx, limit, offset)
138}
139
140func (r *LazyHistoryRepository) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
141	if err := r.init(ctx); err != nil {
142		return nil, err
143	}
144	return r.repo.GetRecentSince(ctx, days)
145}
146
147func (r *LazyHistoryRepository) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
148	if err := r.init(ctx); err != nil {
149		return nil, err
150	}
151	return r.repo.GetMostVisited(ctx, days)
152}
153
154func (r *LazyHistoryRepository) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
155	if err := r.init(ctx); err != nil {
156		return nil, err
157	}
158	return r.repo.GetAllRecentHistory(ctx)
159}
160
161func (r *LazyHistoryRepository) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
162	if err := r.init(ctx); err != nil {
163		return nil, err
164	}
165	return r.repo.GetAllMostVisited(ctx)
166}
167
168func (r *LazyHistoryRepository) IncrementVisitCount(ctx context.Context, url string) error {
169	if err := r.init(ctx); err != nil {
170		return err
171	}
172	return r.repo.IncrementVisitCount(ctx, url)
173}
174
175func (r *LazyHistoryRepository) IncrementVisitCountBy(ctx context.Context, url string, delta int) error {
176	if delta <= 0 {
177		return fmt.Errorf("visit delta must be > 0, got %d", delta)
178	}
179	if err := r.init(ctx); err != nil {
180		return err
181	}
182	if deltaRepo, ok := r.repo.(interface {
183		IncrementVisitCountBy(context.Context, string, int) error
184	}); ok {
185		return deltaRepo.IncrementVisitCountBy(ctx, url, delta)
186	}
187	// Fallback: single-increment loop for repos that don't support delta increment.
188	const fallbackCap = 256
189	if delta > fallbackCap {
190		return fmt.Errorf("visit delta %d exceeds fallback cap %d and repo does not support IncrementVisitCountBy", delta, fallbackCap)
191	}
192	for i := 0; i < delta; i++ {
193		if err := r.repo.IncrementVisitCount(ctx, url); err != nil {
194			return err
195		}
196	}
197	return nil
198}
199
200func (r *LazyHistoryRepository) Delete(ctx context.Context, id int64) error {
201	if err := r.init(ctx); err != nil {
202		return err
203	}
204	return r.repo.Delete(ctx, id)
205}
206
207func (r *LazyHistoryRepository) DeleteOlderThan(ctx context.Context, before time.Time) error {
208	if err := r.init(ctx); err != nil {
209		return err
210	}
211	return r.repo.DeleteOlderThan(ctx, before)
212}
213
214func (r *LazyHistoryRepository) DeleteAll(ctx context.Context) error {
215	if err := r.init(ctx); err != nil {
216		return err
217	}
218	return r.repo.DeleteAll(ctx)
219}
220
221func (r *LazyHistoryRepository) DeleteByDomain(ctx context.Context, domain string) error {
222	if err := r.init(ctx); err != nil {
223		return err
224	}
225	return r.repo.DeleteByDomain(ctx, domain)
226}
227
228func (r *LazyHistoryRepository) GetStats(ctx context.Context) (*entity.HistoryStats, error) {
229	if err := r.init(ctx); err != nil {
230		return nil, err
231	}
232	return r.repo.GetStats(ctx)
233}
234
235func (r *LazyHistoryRepository) GetDomainStats(ctx context.Context, limit int) ([]*entity.DomainStat, error) {
236	if err := r.init(ctx); err != nil {
237		return nil, err
238	}
239	return r.repo.GetDomainStats(ctx, limit)
240}
241
242func (r *LazyHistoryRepository) GetHourlyDistribution(ctx context.Context) ([]*entity.HourlyDistribution, error) {
243	if err := r.init(ctx); err != nil {
244		return nil, err
245	}
246	return r.repo.GetHourlyDistribution(ctx)
247}
248
249func (r *LazyHistoryRepository) GetDailyVisitCount(ctx context.Context, daysAgo string) ([]*entity.DailyVisitCount, error) {
250	if err := r.init(ctx); err != nil {
251		return nil, err
252	}
253	return r.repo.GetDailyVisitCount(ctx, daysAgo)
254}
255
256// LazyFavoriteRepository wraps a favorite repository with lazy database initialization.
257type LazyFavoriteRepository struct {
258	provider port.DatabaseProvider
259	repo     repository.FavoriteRepository
260	once     sync.Once
261	initErr  error
262}
263
264// NewLazyFavoriteRepository creates a lazy-loading favorite repository.
265func NewLazyFavoriteRepository(provider port.DatabaseProvider) repository.FavoriteRepository {
266	return &LazyFavoriteRepository{provider: provider}
267}
268
269func (r *LazyFavoriteRepository) init(ctx context.Context) error {
270	r.once.Do(func() {
271		db, err := r.provider.DB(ctx)
272		if err != nil {
273			r.initErr = err
274			return
275		}
276		r.repo = NewFavoriteRepository(db)
277	})
278	return r.initErr
279}
280
281func (r *LazyFavoriteRepository) Save(ctx context.Context, fav *entity.Favorite) error {
282	if err := r.init(ctx); err != nil {
283		return err
284	}
285	return r.repo.Save(ctx, fav)
286}
287
288func (r *LazyFavoriteRepository) FindByID(ctx context.Context, id entity.FavoriteID) (*entity.Favorite, error) {
289	if err := r.init(ctx); err != nil {
290		return nil, err
291	}
292	return r.repo.FindByID(ctx, id)
293}
294
295func (r *LazyFavoriteRepository) FindByURL(ctx context.Context, url string) (*entity.Favorite, error) {
296	if err := r.init(ctx); err != nil {
297		return nil, err
298	}
299	return r.repo.FindByURL(ctx, url)
300}
301
302func (r *LazyFavoriteRepository) GetAll(ctx context.Context) ([]*entity.Favorite, error) {
303	if err := r.init(ctx); err != nil {
304		return nil, err
305	}
306	return r.repo.GetAll(ctx)
307}
308
309func (r *LazyFavoriteRepository) GetByFolder(ctx context.Context, folderID *entity.FolderID) ([]*entity.Favorite, error) {
310	if err := r.init(ctx); err != nil {
311		return nil, err
312	}
313	return r.repo.GetByFolder(ctx, folderID)
314}
315
316func (r *LazyFavoriteRepository) GetByShortcut(ctx context.Context, key int) (*entity.Favorite, error) {
317	if err := r.init(ctx); err != nil {
318		return nil, err
319	}
320	return r.repo.GetByShortcut(ctx, key)
321}
322
323func (r *LazyFavoriteRepository) UpdatePosition(ctx context.Context, id entity.FavoriteID, position int) error {
324	if err := r.init(ctx); err != nil {
325		return err
326	}
327	return r.repo.UpdatePosition(ctx, id, position)
328}
329
330func (r *LazyFavoriteRepository) SetFolder(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error {
331	if err := r.init(ctx); err != nil {
332		return err
333	}
334	return r.repo.SetFolder(ctx, id, folderID)
335}
336
337func (r *LazyFavoriteRepository) SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error {
338	if err := r.init(ctx); err != nil {
339		return err
340	}
341	return r.repo.SetShortcut(ctx, id, key)
342}
343
344func (r *LazyFavoriteRepository) Delete(ctx context.Context, id entity.FavoriteID) error {
345	if err := r.init(ctx); err != nil {
346		return err
347	}
348	return r.repo.Delete(ctx, id)
349}
350
351// LazyFolderRepository wraps a folder repository with lazy database initialization.
352type LazyFolderRepository struct {
353	provider port.DatabaseProvider
354	repo     repository.FolderRepository
355	once     sync.Once
356	initErr  error
357}
358
359// NewLazyFolderRepository creates a lazy-loading folder repository.
360func NewLazyFolderRepository(provider port.DatabaseProvider) repository.FolderRepository {
361	return &LazyFolderRepository{provider: provider}
362}
363
364func (r *LazyFolderRepository) init(ctx context.Context) error {
365	r.once.Do(func() {
366		db, err := r.provider.DB(ctx)
367		if err != nil {
368			r.initErr = err
369			return
370		}
371		r.repo = NewFolderRepository(db)
372	})
373	return r.initErr
374}
375
376func (r *LazyFolderRepository) Save(ctx context.Context, folder *entity.Folder) error {
377	if err := r.init(ctx); err != nil {
378		return err
379	}
380	return r.repo.Save(ctx, folder)
381}
382
383func (r *LazyFolderRepository) FindByID(ctx context.Context, id entity.FolderID) (*entity.Folder, error) {
384	if err := r.init(ctx); err != nil {
385		return nil, err
386	}
387	return r.repo.FindByID(ctx, id)
388}
389
390func (r *LazyFolderRepository) GetAll(ctx context.Context) ([]*entity.Folder, error) {
391	if err := r.init(ctx); err != nil {
392		return nil, err
393	}
394	return r.repo.GetAll(ctx)
395}
396
397func (r *LazyFolderRepository) GetChildren(ctx context.Context, parentID *entity.FolderID) ([]*entity.Folder, error) {
398	if err := r.init(ctx); err != nil {
399		return nil, err
400	}
401	return r.repo.GetChildren(ctx, parentID)
402}
403
404func (r *LazyFolderRepository) UpdatePosition(ctx context.Context, id entity.FolderID, position int) error {
405	if err := r.init(ctx); err != nil {
406		return err
407	}
408	return r.repo.UpdatePosition(ctx, id, position)
409}
410
411func (r *LazyFolderRepository) Delete(ctx context.Context, id entity.FolderID) error {
412	if err := r.init(ctx); err != nil {
413		return err
414	}
415	return r.repo.Delete(ctx, id)
416}
417
418// LazyTagRepository wraps a tag repository with lazy database initialization.
419type LazyTagRepository struct {
420	provider port.DatabaseProvider
421	repo     repository.TagRepository
422	once     sync.Once
423	initErr  error
424}
425
426// NewLazyTagRepository creates a lazy-loading tag repository.
427func NewLazyTagRepository(provider port.DatabaseProvider) repository.TagRepository {
428	return &LazyTagRepository{provider: provider}
429}
430
431func (r *LazyTagRepository) init(ctx context.Context) error {
432	r.once.Do(func() {
433		db, err := r.provider.DB(ctx)
434		if err != nil {
435			r.initErr = err
436			return
437		}
438		r.repo = NewTagRepository(db)
439	})
440	return r.initErr
441}
442
443func (r *LazyTagRepository) Save(ctx context.Context, tag *entity.Tag) error {
444	if err := r.init(ctx); err != nil {
445		return err
446	}
447	return r.repo.Save(ctx, tag)
448}
449
450func (r *LazyTagRepository) FindByID(ctx context.Context, id entity.TagID) (*entity.Tag, error) {
451	if err := r.init(ctx); err != nil {
452		return nil, err
453	}
454	return r.repo.FindByID(ctx, id)
455}
456
457func (r *LazyTagRepository) FindByName(ctx context.Context, name string) (*entity.Tag, error) {
458	if err := r.init(ctx); err != nil {
459		return nil, err
460	}
461	return r.repo.FindByName(ctx, name)
462}
463
464func (r *LazyTagRepository) GetAll(ctx context.Context) ([]*entity.Tag, error) {
465	if err := r.init(ctx); err != nil {
466		return nil, err
467	}
468	return r.repo.GetAll(ctx)
469}
470
471func (r *LazyTagRepository) AssignToFavorite(ctx context.Context, tagID entity.TagID, favID entity.FavoriteID) error {
472	if err := r.init(ctx); err != nil {
473		return err
474	}
475	return r.repo.AssignToFavorite(ctx, tagID, favID)
476}
477
478func (r *LazyTagRepository) RemoveFromFavorite(ctx context.Context, tagID entity.TagID, favID entity.FavoriteID) error {
479	if err := r.init(ctx); err != nil {
480		return err
481	}
482	return r.repo.RemoveFromFavorite(ctx, tagID, favID)
483}
484
485func (r *LazyTagRepository) GetForFavorite(ctx context.Context, favID entity.FavoriteID) ([]*entity.Tag, error) {
486	if err := r.init(ctx); err != nil {
487		return nil, err
488	}
489	return r.repo.GetForFavorite(ctx, favID)
490}
491
492func (r *LazyTagRepository) Delete(ctx context.Context, id entity.TagID) error {
493	if err := r.init(ctx); err != nil {
494		return err
495	}
496	return r.repo.Delete(ctx, id)
497}
498
499// LazyZoomRepository wraps a zoom repository with lazy database initialization.
500type LazyZoomRepository struct {
501	provider port.DatabaseProvider
502	repo     repository.ZoomRepository
503	once     sync.Once
504	initErr  error
505}
506
507// NewLazyZoomRepository creates a lazy-loading zoom repository.
508func NewLazyZoomRepository(provider port.DatabaseProvider) repository.ZoomRepository {
509	return &LazyZoomRepository{provider: provider}
510}
511
512func (r *LazyZoomRepository) init(ctx context.Context) error {
513	r.once.Do(func() {
514		db, err := r.provider.DB(ctx)
515		if err != nil {
516			r.initErr = err
517			return
518		}
519		r.repo = NewZoomRepository(db)
520	})
521	return r.initErr
522}
523
524func (r *LazyZoomRepository) Get(ctx context.Context, domain string) (*entity.ZoomLevel, error) {
525	if err := r.init(ctx); err != nil {
526		return nil, err
527	}
528	return r.repo.Get(ctx, domain)
529}
530
531func (r *LazyZoomRepository) Set(ctx context.Context, zoom *entity.ZoomLevel) error {
532	if err := r.init(ctx); err != nil {
533		return err
534	}
535	return r.repo.Set(ctx, zoom)
536}
537
538func (r *LazyZoomRepository) Delete(ctx context.Context, domain string) error {
539	if err := r.init(ctx); err != nil {
540		return err
541	}
542	return r.repo.Delete(ctx, domain)
543}
544
545func (r *LazyZoomRepository) GetAll(ctx context.Context) ([]*entity.ZoomLevel, error) {
546	if err := r.init(ctx); err != nil {
547		return nil, err
548	}
549	return r.repo.GetAll(ctx)
550}
551
552// LazySessionRepository wraps a session repository with lazy database initialization.
553type LazySessionRepository struct {
554	provider port.DatabaseProvider
555	repo     repository.SessionRepository
556	once     sync.Once
557	initErr  error
558}
559
560// NewLazySessionRepository creates a lazy-loading session repository.
561func NewLazySessionRepository(provider port.DatabaseProvider) repository.SessionRepository {
562	return &LazySessionRepository{provider: provider}
563}
564
565func (r *LazySessionRepository) init(ctx context.Context) error {
566	r.once.Do(func() {
567		db, err := r.provider.DB(ctx)
568		if err != nil {
569			r.initErr = err
570			return
571		}
572		r.repo = NewSessionRepository(db)
573	})
574	return r.initErr
575}
576
577func (r *LazySessionRepository) Save(ctx context.Context, session *entity.Session) error {
578	if err := r.init(ctx); err != nil {
579		return err
580	}
581	return r.repo.Save(ctx, session)
582}
583
584func (r *LazySessionRepository) FindByID(ctx context.Context, id entity.SessionID) (*entity.Session, error) {
585	if err := r.init(ctx); err != nil {
586		return nil, err
587	}
588	return r.repo.FindByID(ctx, id)
589}
590
591func (r *LazySessionRepository) GetActive(ctx context.Context) (*entity.Session, error) {
592	if err := r.init(ctx); err != nil {
593		return nil, err
594	}
595	return r.repo.GetActive(ctx)
596}
597
598func (r *LazySessionRepository) GetRecent(ctx context.Context, limit int) ([]*entity.Session, error) {
599	if err := r.init(ctx); err != nil {
600		return nil, err
601	}
602	return r.repo.GetRecent(ctx, limit)
603}
604
605func (r *LazySessionRepository) MarkEnded(ctx context.Context, id entity.SessionID, endedAt time.Time) error {
606	if err := r.init(ctx); err != nil {
607		return err
608	}
609	return r.repo.MarkEnded(ctx, id, endedAt)
610}
611
612func (r *LazySessionRepository) Delete(ctx context.Context, id entity.SessionID) error {
613	if err := r.init(ctx); err != nil {
614		return err
615	}
616	return r.repo.Delete(ctx, id)
617}
618
619func (r *LazySessionRepository) DeleteOldestExited(ctx context.Context, keepCount int) (int64, error) {
620	if err := r.init(ctx); err != nil {
621		return 0, err
622	}
623	return r.repo.DeleteOldestExited(ctx, keepCount)
624}
625
626func (r *LazySessionRepository) DeleteExitedBefore(ctx context.Context, cutoff time.Time) (int64, error) {
627	if err := r.init(ctx); err != nil {
628		return 0, err
629	}
630	return r.repo.DeleteExitedBefore(ctx, cutoff)
631}
632
633// LazySessionStateRepository wraps a session state repository with lazy database initialization.
634type LazySessionStateRepository struct {
635	provider port.DatabaseProvider
636	repo     repository.SessionStateRepository
637	once     sync.Once
638	initErr  error
639}
640
641// NewLazySessionStateRepository creates a lazy-loading session state repository.
642func NewLazySessionStateRepository(provider port.DatabaseProvider) repository.SessionStateRepository {
643	return &LazySessionStateRepository{provider: provider}
644}
645
646func (r *LazySessionStateRepository) init(ctx context.Context) error {
647	r.once.Do(func() {
648		db, err := r.provider.DB(ctx)
649		if err != nil {
650			r.initErr = err
651			return
652		}
653		r.repo = NewSessionStateRepository(db)
654	})
655	return r.initErr
656}
657
658func (r *LazySessionStateRepository) SaveSnapshot(ctx context.Context, state *entity.SessionState) error {
659	if err := r.init(ctx); err != nil {
660		return err
661	}
662	return r.repo.SaveSnapshot(ctx, state)
663}
664
665func (r *LazySessionStateRepository) GetSnapshot(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error) {
666	if err := r.init(ctx); err != nil {
667		return nil, err
668	}
669	return r.repo.GetSnapshot(ctx, sessionID)
670}
671
672func (r *LazySessionStateRepository) DeleteSnapshot(ctx context.Context, sessionID entity.SessionID) error {
673	if err := r.init(ctx); err != nil {
674		return err
675	}
676	return r.repo.DeleteSnapshot(ctx, sessionID)
677}
678
679func (r *LazySessionStateRepository) GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error) {
680	if err := r.init(ctx); err != nil {
681		return nil, err
682	}
683	return r.repo.GetAllSnapshots(ctx)
684}
685
686func (r *LazySessionStateRepository) GetTotalSnapshotsSize(ctx context.Context) (int64, error) {
687	if err := r.init(ctx); err != nil {
688		return 0, err
689	}
690	return r.repo.GetTotalSnapshotsSize(ctx)
691}
692
693// LazyContentWhitelistRepository wraps a content whitelist repository with lazy database initialization.
694type LazyContentWhitelistRepository struct {
695	provider port.DatabaseProvider
696	repo     repository.ContentWhitelistRepository
697	once     sync.Once
698	initErr  error
699}
700
701// NewLazyContentWhitelistRepository creates a lazy-loading content whitelist repository.
702func NewLazyContentWhitelistRepository(provider port.DatabaseProvider) repository.ContentWhitelistRepository {
703	return &LazyContentWhitelistRepository{provider: provider}
704}
705
706func (r *LazyContentWhitelistRepository) init(ctx context.Context) error {
707	r.once.Do(func() {
708		db, err := r.provider.DB(ctx)
709		if err != nil {
710			r.initErr = err
711			return
712		}
713		r.repo = NewContentWhitelistRepository(db)
714	})
715	return r.initErr
716}
717
718func (r *LazyContentWhitelistRepository) Add(ctx context.Context, domain string) error {
719	if err := r.init(ctx); err != nil {
720		return err
721	}
722	return r.repo.Add(ctx, domain)
723}
724
725func (r *LazyContentWhitelistRepository) Remove(ctx context.Context, domain string) error {
726	if err := r.init(ctx); err != nil {
727		return err
728	}
729	return r.repo.Remove(ctx, domain)
730}
731
732func (r *LazyContentWhitelistRepository) Contains(ctx context.Context, domain string) (bool, error) {
733	if err := r.init(ctx); err != nil {
734		return false, err
735	}
736	return r.repo.Contains(ctx, domain)
737}
738
739func (r *LazyContentWhitelistRepository) GetAll(ctx context.Context) ([]string, error) {
740	if err := r.init(ctx); err != nil {
741		return nil, err
742	}
743	return r.repo.GetAll(ctx)
744}
745
746// LazyRepositories holds all lazy-loaded repositories.
747type LazyRepositories struct {
748	History      repository.HistoryRepository
749	Favorite     repository.FavoriteRepository
750	Folder       repository.FolderRepository
751	Tag          repository.TagRepository
752	Zoom         repository.ZoomRepository
753	Session      repository.SessionRepository
754	SessionState repository.SessionStateRepository
755	Filter       repository.ContentWhitelistRepository
756	Permission   port.PermissionRepository
757}
758
759// NewLazyRepositories creates all lazy repositories from a database provider.
760func NewLazyRepositories(provider port.DatabaseProvider) *LazyRepositories {
761	return &LazyRepositories{
762		History:      NewLazyHistoryRepository(provider),
763		Favorite:     NewLazyFavoriteRepository(provider),
764		Folder:       NewLazyFolderRepository(provider),
765		Tag:          NewLazyTagRepository(provider),
766		Zoom:         NewLazyZoomRepository(provider),
767		Session:      NewLazySessionRepository(provider),
768		SessionState: NewLazySessionStateRepository(provider),
769		Filter:       NewLazyContentWhitelistRepository(provider),
770		Permission:   NewLazyPermissionRepository(provider),
771	}
772}
773
774// GetDB returns the underlying *sql.DB for operations that need it directly.
775// Returns nil if the database hasn't been initialized yet.
776func GetDB(provider port.DatabaseProvider, ctx context.Context) *sql.DB {
777	db, err := provider.DB(ctx)
778	if err != nil {
779		return nil
780	}
781	return db
782}

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