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) UpdateMetadata(ctx context.Context, entry *entity.HistoryEntry) error {
127 if err := r.init(ctx); err != nil {
128 return err
129 }
130 if updater, ok := r.repo.(interface {
131 UpdateMetadata(context.Context, *entity.HistoryEntry) error
132 }); ok {
133 return updater.UpdateMetadata(ctx, entry)
134 }
135 return r.repo.Save(ctx, entry)
136}
137
138func (r *LazyHistoryRepository) Search(
139 ctx context.Context,
140 query string,
141 limit int,
142 scope repository.HistoryScope,
143) ([]entity.HistoryMatch, error) {
144 if err := r.init(ctx); err != nil {
145 return nil, err
146 }
147 return r.repo.Search(ctx, query, limit, scope)
148}
149
150func (r *LazyHistoryRepository) GetRecent(
151 ctx context.Context,
152 limit, offset int,
153 scope repository.HistoryScope,
154) ([]*entity.HistoryEntry, error) {
155 if err := r.init(ctx); err != nil {
156 return nil, err
157 }
158 return r.repo.GetRecent(ctx, limit, offset, scope)
159}
160
161func (r *LazyHistoryRepository) GetRecentByDomain(ctx context.Context, domain string, limit, offset int) ([]*entity.HistoryEntry, error) {
162 if err := r.init(ctx); err != nil {
163 return nil, err
164 }
165 return r.repo.GetRecentByDomain(ctx, domain, limit, offset)
166}
167
168func (r *LazyHistoryRepository) GetRecentWindow(
169 ctx context.Context,
170 before time.Time,
171 beforeID int64,
172 limit int,
173) ([]*entity.HistoryEntry, error) {
174 if err := r.init(ctx); err != nil {
175 return nil, err
176 }
177 return r.repo.GetRecentWindow(ctx, before, beforeID, limit)
178}
179
180func (r *LazyHistoryRepository) GetRecentWindowByDomain(
181 ctx context.Context,
182 domain string,
183 before time.Time,
184 beforeID int64,
185 limit int,
186) ([]*entity.HistoryEntry, error) {
187 if err := r.init(ctx); err != nil {
188 return nil, err
189 }
190 return r.repo.GetRecentWindowByDomain(ctx, domain, before, beforeID, limit)
191}
192
193func (r *LazyHistoryRepository) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
194 if err := r.init(ctx); err != nil {
195 return nil, err
196 }
197 return r.repo.GetRecentSince(ctx, days)
198}
199
200func (r *LazyHistoryRepository) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
201 if err := r.init(ctx); err != nil {
202 return nil, err
203 }
204 return r.repo.GetMostVisited(ctx, days)
205}
206
207func (r *LazyHistoryRepository) GetMostVisitedWithin(
208 ctx context.Context,
209 limit int,
210 scope repository.HistoryScope,
211) ([]*entity.HistoryEntry, error) {
212 if err := r.init(ctx); err != nil {
213 return nil, err
214 }
215 return r.repo.GetMostVisitedWithin(ctx, limit, scope)
216}
217
218func (r *LazyHistoryRepository) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
219 if err := r.init(ctx); err != nil {
220 return nil, err
221 }
222 return r.repo.GetAllRecentHistory(ctx)
223}
224
225func (r *LazyHistoryRepository) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
226 if err := r.init(ctx); err != nil {
227 return nil, err
228 }
229 return r.repo.GetAllMostVisited(ctx)
230}
231
232func (r *LazyHistoryRepository) IncrementVisitCount(ctx context.Context, url string) error {
233 if err := r.init(ctx); err != nil {
234 return err
235 }
236 return r.repo.IncrementVisitCount(ctx, url)
237}
238
239func (r *LazyHistoryRepository) IncrementVisitCountBy(ctx context.Context, url string, delta int) error {
240 if delta <= 0 {
241 return fmt.Errorf("visit delta must be > 0, got %d", delta)
242 }
243 if err := r.init(ctx); err != nil {
244 return err
245 }
246 if deltaRepo, ok := r.repo.(interface {
247 IncrementVisitCountBy(context.Context, string, int) error
248 }); ok {
249 return deltaRepo.IncrementVisitCountBy(ctx, url, delta)
250 }
251 // Fallback: single-increment loop for repos that don't support delta increment.
252 const fallbackCap = 256
253 if delta > fallbackCap {
254 return fmt.Errorf("visit delta %d exceeds fallback cap %d and repo does not support IncrementVisitCountBy", delta, fallbackCap)
255 }
256 for range delta {
257 if err := r.repo.IncrementVisitCount(ctx, url); err != nil {
258 return err
259 }
260 }
261 return nil
262}
263
264func (r *LazyHistoryRepository) Delete(ctx context.Context, id int64) error {
265 if err := r.init(ctx); err != nil {
266 return err
267 }
268 return r.repo.Delete(ctx, id)
269}
270
271func (r *LazyHistoryRepository) DeleteOlderThan(ctx context.Context, before time.Time) error {
272 if err := r.init(ctx); err != nil {
273 return err
274 }
275 return r.repo.DeleteOlderThan(ctx, before)
276}
277
278func (r *LazyHistoryRepository) DeleteSince(ctx context.Context, since time.Time) error {
279 if err := r.init(ctx); err != nil {
280 return err
281 }
282 return r.repo.DeleteSince(ctx, since)
283}
284
285func (r *LazyHistoryRepository) DeleteAll(ctx context.Context) error {
286 if err := r.init(ctx); err != nil {
287 return err
288 }
289 return r.repo.DeleteAll(ctx)
290}
291
292func (r *LazyHistoryRepository) DeleteByDomain(ctx context.Context, domain string) error {
293 if err := r.init(ctx); err != nil {
294 return err
295 }
296 return r.repo.DeleteByDomain(ctx, domain)
297}
298
299func (r *LazyHistoryRepository) GetStats(ctx context.Context) (*entity.HistoryStats, error) {
300 if err := r.init(ctx); err != nil {
301 return nil, err
302 }
303 return r.repo.GetStats(ctx)
304}
305
306func (r *LazyHistoryRepository) GetDomainStats(ctx context.Context, limit int) ([]*entity.DomainStat, error) {
307 if err := r.init(ctx); err != nil {
308 return nil, err
309 }
310 return r.repo.GetDomainStats(ctx, limit)
311}
312
313func (r *LazyHistoryRepository) GetHourlyDistribution(ctx context.Context) ([]*entity.HourlyDistribution, error) {
314 if err := r.init(ctx); err != nil {
315 return nil, err
316 }
317 return r.repo.GetHourlyDistribution(ctx)
318}
319
320func (r *LazyHistoryRepository) GetDailyVisitCount(ctx context.Context, daysAgo string) ([]*entity.DailyVisitCount, error) {
321 if err := r.init(ctx); err != nil {
322 return nil, err
323 }
324 return r.repo.GetDailyVisitCount(ctx, daysAgo)
325}
326
327// LazyFavoriteRepository wraps a favorite repository with lazy database initialization.
328type LazyFavoriteRepository struct {
329 provider port.DatabaseProvider
330 repo repository.FavoriteRepository
331 once sync.Once
332 initErr error
333}
334
335// NewLazyFavoriteRepository creates a lazy-loading favorite repository.
336func NewLazyFavoriteRepository(provider port.DatabaseProvider) repository.FavoriteRepository {
337 return &LazyFavoriteRepository{provider: provider}
338}
339
340func (r *LazyFavoriteRepository) init(ctx context.Context) error {
341 r.once.Do(func() {
342 db, err := r.provider.DB(ctx)
343 if err != nil {
344 r.initErr = err
345 return
346 }
347 r.repo = NewFavoriteRepository(db)
348 })
349 return r.initErr
350}
351
352func (r *LazyFavoriteRepository) Save(ctx context.Context, fav *entity.Favorite) error {
353 if err := r.init(ctx); err != nil {
354 return err
355 }
356 return r.repo.Save(ctx, fav)
357}
358
359func (r *LazyFavoriteRepository) FindByID(ctx context.Context, id entity.FavoriteID) (*entity.Favorite, error) {
360 if err := r.init(ctx); err != nil {
361 return nil, err
362 }
363 return r.repo.FindByID(ctx, id)
364}
365
366func (r *LazyFavoriteRepository) FindByURL(ctx context.Context, url string) (*entity.Favorite, error) {
367 if err := r.init(ctx); err != nil {
368 return nil, err
369 }
370 return r.repo.FindByURL(ctx, url)
371}
372
373func (r *LazyFavoriteRepository) GetAll(ctx context.Context) ([]*entity.Favorite, error) {
374 if err := r.init(ctx); err != nil {
375 return nil, err
376 }
377 return r.repo.GetAll(ctx)
378}
379
380func (r *LazyFavoriteRepository) GetByTag(ctx context.Context, tagID entity.TagID) ([]*entity.Favorite, error) {
381 if err := r.init(ctx); err != nil {
382 return nil, err
383 }
384 return r.repo.GetByTag(ctx, tagID)
385}
386
387func (r *LazyFavoriteRepository) GetByShortcut(ctx context.Context, key int) (*entity.Favorite, error) {
388 if err := r.init(ctx); err != nil {
389 return nil, err
390 }
391 return r.repo.GetByShortcut(ctx, key)
392}
393
394func (r *LazyFavoriteRepository) UpdatePosition(ctx context.Context, id entity.FavoriteID, position int) error {
395 if err := r.init(ctx); err != nil {
396 return err
397 }
398 return r.repo.UpdatePosition(ctx, id, position)
399}
400
401func (r *LazyFavoriteRepository) SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error {
402 if err := r.init(ctx); err != nil {
403 return err
404 }
405 return r.repo.SetShortcut(ctx, id, key)
406}
407
408func (r *LazyFavoriteRepository) Delete(ctx context.Context, id entity.FavoriteID) error {
409 if err := r.init(ctx); err != nil {
410 return err
411 }
412 return r.repo.Delete(ctx, id)
413}
414
415// LazyTagRepository wraps a tag repository with lazy database initialization.
416type LazyTagRepository struct {
417 provider port.DatabaseProvider
418 repo repository.TagRepository
419 once sync.Once
420 initErr error
421}
422
423// NewLazyTagRepository creates a lazy-loading tag repository.
424func NewLazyTagRepository(provider port.DatabaseProvider) repository.TagRepository {
425 return &LazyTagRepository{provider: provider}
426}
427
428func (r *LazyTagRepository) init(ctx context.Context) error {
429 r.once.Do(func() {
430 db, err := r.provider.DB(ctx)
431 if err != nil {
432 r.initErr = err
433 return
434 }
435 r.repo = NewTagRepository(db)
436 })
437 return r.initErr
438}
439
440func (r *LazyTagRepository) Save(ctx context.Context, tag *entity.Tag) error {
441 if err := r.init(ctx); err != nil {
442 return err
443 }
444 return r.repo.Save(ctx, tag)
445}
446
447func (r *LazyTagRepository) FindByID(ctx context.Context, id entity.TagID) (*entity.Tag, error) {
448 if err := r.init(ctx); err != nil {
449 return nil, err
450 }
451 return r.repo.FindByID(ctx, id)
452}
453
454func (r *LazyTagRepository) FindByName(ctx context.Context, name string) (*entity.Tag, error) {
455 if err := r.init(ctx); err != nil {
456 return nil, err
457 }
458 return r.repo.FindByName(ctx, name)
459}
460
461func (r *LazyTagRepository) GetAll(ctx context.Context) ([]*entity.Tag, error) {
462 if err := r.init(ctx); err != nil {
463 return nil, err
464 }
465 return r.repo.GetAll(ctx)
466}
467
468func (r *LazyTagRepository) AssignToFavorite(ctx context.Context, tagID entity.TagID, favID entity.FavoriteID) error {
469 if err := r.init(ctx); err != nil {
470 return err
471 }
472 return r.repo.AssignToFavorite(ctx, tagID, favID)
473}
474
475func (r *LazyTagRepository) RemoveFromFavorite(ctx context.Context, tagID entity.TagID, favID entity.FavoriteID) error {
476 if err := r.init(ctx); err != nil {
477 return err
478 }
479 return r.repo.RemoveFromFavorite(ctx, tagID, favID)
480}
481
482func (r *LazyTagRepository) GetForFavorite(ctx context.Context, favID entity.FavoriteID) ([]*entity.Tag, error) {
483 if err := r.init(ctx); err != nil {
484 return nil, err
485 }
486 return r.repo.GetForFavorite(ctx, favID)
487}
488
489func (r *LazyTagRepository) Delete(ctx context.Context, id entity.TagID) error {
490 if err := r.init(ctx); err != nil {
491 return err
492 }
493 return r.repo.Delete(ctx, id)
494}
495
496// LazyZoomRepository wraps a zoom repository with lazy database initialization.
497type LazyZoomRepository struct {
498 provider port.DatabaseProvider
499 repo repository.ZoomRepository
500 once sync.Once
501 initErr error
502}
503
504// NewLazyZoomRepository creates a lazy-loading zoom repository.
505func NewLazyZoomRepository(provider port.DatabaseProvider) repository.ZoomRepository {
506 return &LazyZoomRepository{provider: provider}
507}
508
509func (r *LazyZoomRepository) init(ctx context.Context) error {
510 r.once.Do(func() {
511 db, err := r.provider.DB(ctx)
512 if err != nil {
513 r.initErr = err
514 return
515 }
516 r.repo = NewZoomRepository(db)
517 })
518 return r.initErr
519}
520
521func (r *LazyZoomRepository) Get(ctx context.Context, domain string) (*entity.ZoomLevel, error) {
522 if err := r.init(ctx); err != nil {
523 return nil, err
524 }
525 return r.repo.Get(ctx, domain)
526}
527
528func (r *LazyZoomRepository) Set(ctx context.Context, zoom *entity.ZoomLevel) error {
529 if err := r.init(ctx); err != nil {
530 return err
531 }
532 return r.repo.Set(ctx, zoom)
533}
534
535func (r *LazyZoomRepository) Delete(ctx context.Context, domain string) error {
536 if err := r.init(ctx); err != nil {
537 return err
538 }
539 return r.repo.Delete(ctx, domain)
540}
541
542func (r *LazyZoomRepository) GetAll(ctx context.Context) ([]*entity.ZoomLevel, error) {
543 if err := r.init(ctx); err != nil {
544 return nil, err
545 }
546 return r.repo.GetAll(ctx)
547}
548
549// LazySessionRepository wraps a session repository with lazy database initialization.
550type LazySessionRepository struct {
551 provider port.DatabaseProvider
552 repo repository.SessionRepository
553 once sync.Once
554 initErr error
555}
556
557// NewLazySessionRepository creates a lazy-loading session repository.
558func NewLazySessionRepository(provider port.DatabaseProvider) repository.SessionRepository {
559 return &LazySessionRepository{provider: provider}
560}
561
562func (r *LazySessionRepository) init(ctx context.Context) error {
563 r.once.Do(func() {
564 db, err := r.provider.DB(ctx)
565 if err != nil {
566 r.initErr = err
567 return
568 }
569 r.repo = NewSessionRepository(db)
570 })
571 return r.initErr
572}
573
574func (r *LazySessionRepository) Save(ctx context.Context, session *entity.Session) error {
575 if err := r.init(ctx); err != nil {
576 return err
577 }
578 return r.repo.Save(ctx, session)
579}
580
581func (r *LazySessionRepository) FindByID(ctx context.Context, id entity.SessionID) (*entity.Session, error) {
582 if err := r.init(ctx); err != nil {
583 return nil, err
584 }
585 return r.repo.FindByID(ctx, id)
586}
587
588func (r *LazySessionRepository) GetActive(ctx context.Context) (*entity.Session, error) {
589 if err := r.init(ctx); err != nil {
590 return nil, err
591 }
592 return r.repo.GetActive(ctx)
593}
594
595func (r *LazySessionRepository) GetRecent(ctx context.Context, limit int) ([]*entity.Session, error) {
596 if err := r.init(ctx); err != nil {
597 return nil, err
598 }
599 return r.repo.GetRecent(ctx, limit)
600}
601
602func (r *LazySessionRepository) MarkEnded(ctx context.Context, id entity.SessionID, endedAt time.Time) error {
603 if err := r.init(ctx); err != nil {
604 return err
605 }
606 return r.repo.MarkEnded(ctx, id, endedAt)
607}
608
609func (r *LazySessionRepository) Delete(ctx context.Context, id entity.SessionID) error {
610 if err := r.init(ctx); err != nil {
611 return err
612 }
613 return r.repo.Delete(ctx, id)
614}
615
616func (r *LazySessionRepository) DeleteOldestExited(ctx context.Context, keepCount int) (int64, error) {
617 if err := r.init(ctx); err != nil {
618 return 0, err
619 }
620 return r.repo.DeleteOldestExited(ctx, keepCount)
621}
622
623func (r *LazySessionRepository) DeleteExitedBefore(ctx context.Context, cutoff time.Time) (int64, error) {
624 if err := r.init(ctx); err != nil {
625 return 0, err
626 }
627 return r.repo.DeleteExitedBefore(ctx, cutoff)
628}
629
630// LazySessionStateRepository wraps a session state repository with lazy database initialization.
631type LazySessionStateRepository struct {
632 provider port.DatabaseProvider
633 repo repository.SessionStateRepository
634 once sync.Once
635 initErr error
636}
637
638// NewLazySessionStateRepository creates a lazy-loading session state repository.
639func NewLazySessionStateRepository(provider port.DatabaseProvider) repository.SessionStateRepository {
640 return &LazySessionStateRepository{provider: provider}
641}
642
643func (r *LazySessionStateRepository) init(ctx context.Context) error {
644 r.once.Do(func() {
645 db, err := r.provider.DB(ctx)
646 if err != nil {
647 r.initErr = err
648 return
649 }
650 r.repo = NewSessionStateRepository(db)
651 })
652 return r.initErr
653}
654
655func (r *LazySessionStateRepository) SaveSnapshot(ctx context.Context, state *entity.SessionState) error {
656 if err := r.init(ctx); err != nil {
657 return err
658 }
659 return r.repo.SaveSnapshot(ctx, state)
660}
661
662func (r *LazySessionStateRepository) GetSnapshot(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error) {
663 if err := r.init(ctx); err != nil {
664 return nil, err
665 }
666 return r.repo.GetSnapshot(ctx, sessionID)
667}
668
669func (r *LazySessionStateRepository) DeleteSnapshot(ctx context.Context, sessionID entity.SessionID) error {
670 if err := r.init(ctx); err != nil {
671 return err
672 }
673 return r.repo.DeleteSnapshot(ctx, sessionID)
674}
675
676func (r *LazySessionStateRepository) GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error) {
677 if err := r.init(ctx); err != nil {
678 return nil, err
679 }
680 return r.repo.GetAllSnapshots(ctx)
681}
682
683func (r *LazySessionStateRepository) GetTotalSnapshotsSize(ctx context.Context) (int64, error) {
684 if err := r.init(ctx); err != nil {
685 return 0, err
686 }
687 return r.repo.GetTotalSnapshotsSize(ctx)
688}
689
690// LazyContentWhitelistRepository wraps a content whitelist repository with lazy database initialization.
691type LazyContentWhitelistRepository struct {
692 provider port.DatabaseProvider
693 repo repository.ContentWhitelistRepository
694 once sync.Once
695 initErr error
696}
697
698// NewLazyContentWhitelistRepository creates a lazy-loading content whitelist repository.
699func NewLazyContentWhitelistRepository(provider port.DatabaseProvider) repository.ContentWhitelistRepository {
700 return &LazyContentWhitelistRepository{provider: provider}
701}
702
703func (r *LazyContentWhitelistRepository) init(ctx context.Context) error {
704 r.once.Do(func() {
705 db, err := r.provider.DB(ctx)
706 if err != nil {
707 r.initErr = err
708 return
709 }
710 r.repo = NewContentWhitelistRepository(db)
711 })
712 return r.initErr
713}
714
715func (r *LazyContentWhitelistRepository) Add(ctx context.Context, domain string) error {
716 if err := r.init(ctx); err != nil {
717 return err
718 }
719 return r.repo.Add(ctx, domain)
720}
721
722func (r *LazyContentWhitelistRepository) Remove(ctx context.Context, domain string) error {
723 if err := r.init(ctx); err != nil {
724 return err
725 }
726 return r.repo.Remove(ctx, domain)
727}
728
729func (r *LazyContentWhitelistRepository) Contains(ctx context.Context, domain string) (bool, error) {
730 if err := r.init(ctx); err != nil {
731 return false, err
732 }
733 return r.repo.Contains(ctx, domain)
734}
735
736func (r *LazyContentWhitelistRepository) GetAll(ctx context.Context) ([]string, error) {
737 if err := r.init(ctx); err != nil {
738 return nil, err
739 }
740 return r.repo.GetAll(ctx)
741}
742
743// LazyRepositories holds all lazy-loaded repositories.
744type LazyRepositories struct {
745 History repository.HistoryRepository
746 Favorite repository.FavoriteRepository
747 Tag repository.TagRepository
748 Zoom repository.ZoomRepository
749 Session repository.SessionRepository
750 SessionState repository.SessionStateRepository
751 Filter repository.ContentWhitelistRepository
752 Permission port.PermissionRepository
753}
754
755// NewLazyRepositories creates all lazy repositories from a database provider.
756func NewLazyRepositories(provider port.DatabaseProvider) *LazyRepositories {
757 return &LazyRepositories{
758 History: NewLazyHistoryRepository(provider),
759 Favorite: NewLazyFavoriteRepository(provider),
760 Tag: NewLazyTagRepository(provider),
761 Zoom: NewLazyZoomRepository(provider),
762 Session: NewLazySessionRepository(provider),
763 SessionState: NewLazySessionStateRepository(provider),
764 Filter: NewLazyContentWhitelistRepository(provider),
765 Permission: NewLazyPermissionRepository(provider),
766 }
767}
768
769// GetDB returns the underlying *sql.DB for operations that need it directly.
770// Returns nil if the database hasn't been initialized yet.
771func GetDB(provider port.DatabaseProvider, ctx context.Context) *sql.DB {
772 db, err := provider.DB(ctx)
773 if err != nil {
774 return nil
775 }
776 return db
777}