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}