1package sqlite
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "sync"
8 "time"
9
10 "github.com/bnema/dumber/internal/application/port"
11 "github.com/bnema/dumber/internal/domain/favicon"
12 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite/sqlc"
13)
14
15type faviconRepo struct {
16 queries *sqlc.Queries
17}
18
19func NewFaviconRepository(db *sql.DB) port.FaviconRepository {
20 return &faviconRepo{queries: sqlc.New(db)}
21}
22
23type lazyFaviconRepository struct {
24 provider port.DatabaseProvider
25 repo port.FaviconRepository
26 once sync.Once
27 initErr error
28}
29
30func NewLazyFaviconRepository(provider port.DatabaseProvider) port.FaviconRepository {
31 return &lazyFaviconRepository{provider: provider}
32}
33
34func (r *lazyFaviconRepository) init(ctx context.Context) error {
35 r.once.Do(func() {
36 db, err := r.provider.DB(ctx)
37 if err != nil {
38 r.initErr = err
39 return
40 }
41 r.repo = NewFaviconRepository(db)
42 })
43 return r.initErr
44}
45
46func (r *lazyFaviconRepository) Get(ctx context.Context, key favicon.Key) (*favicon.Metadata, error) {
47 if err := r.init(ctx); err != nil {
48 return nil, err
49 }
50 return r.repo.Get(ctx, key)
51}
52
53func (r *lazyFaviconRepository) FindFirst(ctx context.Context, keys []favicon.Key) (*favicon.Metadata, error) {
54 if err := r.init(ctx); err != nil {
55 return nil, err
56 }
57 return r.repo.FindFirst(ctx, keys)
58}
59
60func (r *lazyFaviconRepository) Upsert(ctx context.Context, meta favicon.Metadata) error {
61 if err := r.init(ctx); err != nil {
62 return err
63 }
64 return r.repo.Upsert(ctx, meta)
65}
66
67func (r *lazyFaviconRepository) UpdateLastChecked(ctx context.Context, key favicon.Key, contentHash string, checkedAt time.Time) error {
68 if err := r.init(ctx); err != nil {
69 return err
70 }
71 return r.repo.UpdateLastChecked(ctx, key, contentHash, checkedAt)
72}
73
74func (r *lazyFaviconRepository) Delete(ctx context.Context, key favicon.Key) error {
75 if err := r.init(ctx); err != nil {
76 return err
77 }
78 return r.repo.Delete(ctx, key)
79}
80
81func (r *faviconRepo) Get(ctx context.Context, key favicon.Key) (*favicon.Metadata, error) {
82 row, err := r.queries.GetFavicon(ctx, string(key))
83 if err != nil {
84 if errors.Is(err, sql.ErrNoRows) {
85 return nil, nil
86 }
87 return nil, err
88 }
89 return faviconFromRow(row), nil
90}
91
92func (r *faviconRepo) FindFirst(ctx context.Context, keys []favicon.Key) (*favicon.Metadata, error) {
93 for _, key := range keys {
94 meta, err := r.Get(ctx, key)
95 if err != nil {
96 return nil, err
97 }
98 if meta != nil {
99 return meta, nil
100 }
101 }
102 return nil, nil
103}
104
105func (r *faviconRepo) Upsert(ctx context.Context, meta favicon.Metadata) error {
106 return r.queries.UpsertFavicon(ctx, sqlc.UpsertFaviconParams{
107 Key: string(meta.Key),
108 SourceUrl: nullString(meta.SourceURL),
109 PageUrl: nullString(meta.PageURL),
110 Source: string(meta.Source),
111 ContentHash: meta.ContentHash,
112 ContentType: nullString(meta.ContentType),
113 UpdatedAt: meta.UpdatedAt,
114 LastCheckedAt: meta.LastCheckedAt,
115 })
116}
117
118func (r *faviconRepo) UpdateLastChecked(ctx context.Context, key favicon.Key, contentHash string, checkedAt time.Time) error {
119 return r.queries.UpdateFaviconLastChecked(ctx, sqlc.UpdateFaviconLastCheckedParams{
120 Key: string(key),
121 ContentHash: contentHash,
122 LastCheckedAt: checkedAt,
123 })
124}
125
126func (r *faviconRepo) Delete(ctx context.Context, key favicon.Key) error {
127 return r.queries.DeleteFavicon(ctx, string(key))
128}
129
130func faviconFromRow(row sqlc.Favicon) *favicon.Metadata {
131 return &favicon.Metadata{
132 Key: favicon.Key(row.Key),
133 SourceURL: row.SourceUrl.String,
134 PageURL: row.PageUrl.String,
135 Source: favicon.Source(row.Source),
136 ContentHash: row.ContentHash,
137 ContentType: row.ContentType.String,
138 UpdatedAt: row.UpdatedAt,
139 LastCheckedAt: row.LastCheckedAt,
140 }
141}