forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package db
2
3import (
4 "fmt"
5 "log"
6 "strings"
7 "time"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10)
11
12type Star struct {
13 StarredByDid string
14 RepoAt syntax.ATURI
15 Created time.Time
16 Rkey string
17
18 // optionally, populate this when querying for reverse mappings
19 Repo *Repo
20}
21
22func (star *Star) ResolveRepo(e Execer) error {
23 if star.Repo != nil {
24 return nil
25 }
26
27 repo, err := GetRepoByAtUri(e, star.RepoAt.String())
28 if err != nil {
29 return err
30 }
31
32 star.Repo = repo
33 return nil
34}
35
36func AddStar(e Execer, starredByDid string, repoAt syntax.ATURI, rkey string) error {
37 query := `insert or ignore into stars (starred_by_did, repo_at, rkey) values (?, ?, ?)`
38 _, err := e.Exec(query, starredByDid, repoAt, rkey)
39 return err
40}
41
42// Get a star record
43func GetStar(e Execer, starredByDid string, repoAt syntax.ATURI) (*Star, error) {
44 query := `
45 select starred_by_did, repo_at, created, rkey
46 from stars
47 where starred_by_did = ? and repo_at = ?`
48 row := e.QueryRow(query, starredByDid, repoAt)
49
50 var star Star
51 var created string
52 err := row.Scan(&star.StarredByDid, &star.RepoAt, &created, &star.Rkey)
53 if err != nil {
54 return nil, err
55 }
56
57 createdAtTime, err := time.Parse(time.RFC3339, created)
58 if err != nil {
59 log.Println("unable to determine followed at time")
60 star.Created = time.Now()
61 } else {
62 star.Created = createdAtTime
63 }
64
65 return &star, nil
66}
67
68// Remove a star
69func DeleteStar(e Execer, starredByDid string, repoAt syntax.ATURI) error {
70 _, err := e.Exec(`delete from stars where starred_by_did = ? and repo_at = ?`, starredByDid, repoAt)
71 return err
72}
73
74// Remove a star
75func DeleteStarByRkey(e Execer, starredByDid string, rkey string) error {
76 _, err := e.Exec(`delete from stars where starred_by_did = ? and rkey = ?`, starredByDid, rkey)
77 return err
78}
79
80func GetStarCount(e Execer, repoAt syntax.ATURI) (int, error) {
81 stars := 0
82 err := e.QueryRow(
83 `select count(starred_by_did) from stars where repo_at = ?`, repoAt).Scan(&stars)
84 if err != nil {
85 return 0, err
86 }
87 return stars, nil
88}
89
90func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool {
91 if _, err := GetStar(e, userDid, repoAt); err != nil {
92 return false
93 } else {
94 return true
95 }
96}
97
98func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) {
99 var conditions []string
100 var args []any
101 for _, filter := range filters {
102 conditions = append(conditions, filter.Condition())
103 args = append(args, filter.Arg()...)
104 }
105
106 whereClause := ""
107 if conditions != nil {
108 whereClause = " where " + strings.Join(conditions, " and ")
109 }
110
111 limitClause := ""
112 if limit != 0 {
113 limitClause = fmt.Sprintf(" limit %d", limit)
114 }
115
116 repoQuery := fmt.Sprintf(
117 `select starred_by_did, repo_at, created, rkey
118 from stars
119 %s
120 order by created desc
121 %s`,
122 whereClause,
123 limitClause,
124 )
125 rows, err := e.Query(repoQuery, args...)
126 if err != nil {
127 return nil, err
128 }
129
130 starMap := make(map[string][]Star)
131 for rows.Next() {
132 var star Star
133 var created string
134 err := rows.Scan(&star.StarredByDid, &star.RepoAt, &created, &star.Rkey)
135 if err != nil {
136 return nil, err
137 }
138
139 star.Created = time.Now()
140 if t, err := time.Parse(time.RFC3339, created); err == nil {
141 star.Created = t
142 }
143
144 repoAt := string(star.RepoAt)
145 starMap[repoAt] = append(starMap[repoAt], star)
146 }
147
148 // populate *Repo in each star
149 args = make([]any, len(starMap))
150 i := 0
151 for r := range starMap {
152 args[i] = r
153 i++
154 }
155
156 if len(args) == 0 {
157 return nil, nil
158 }
159
160 repos, err := GetRepos(e, 0, FilterIn("at_uri", args))
161 if err != nil {
162 return nil, err
163 }
164
165 for _, r := range repos {
166 if stars, ok := starMap[string(r.RepoAt())]; ok {
167 for i := range stars {
168 stars[i].Repo = &r
169 }
170 }
171 }
172
173 var stars []Star
174 for _, s := range starMap {
175 stars = append(stars, s...)
176 }
177
178 return stars, nil
179}
180
181func GetAllStars(e Execer, limit int) ([]Star, error) {
182 var stars []Star
183
184 rows, err := e.Query(`
185 select
186 s.starred_by_did,
187 s.repo_at,
188 s.rkey,
189 s.created,
190 r.did,
191 r.name,
192 r.knot,
193 r.rkey,
194 r.created,
195 r.at_uri
196 from stars s
197 join repos r on s.repo_at = r.at_uri
198 `)
199
200 if err != nil {
201 return nil, err
202 }
203 defer rows.Close()
204
205 for rows.Next() {
206 var star Star
207 var repo Repo
208 var starCreatedAt, repoCreatedAt string
209
210 if err := rows.Scan(
211 &star.StarredByDid,
212 &star.RepoAt,
213 &star.Rkey,
214 &starCreatedAt,
215 &repo.Did,
216 &repo.Name,
217 &repo.Knot,
218 &repo.Rkey,
219 &repoCreatedAt,
220 &repo.AtUri,
221 ); err != nil {
222 return nil, err
223 }
224
225 star.Created, err = time.Parse(time.RFC3339, starCreatedAt)
226 if err != nil {
227 star.Created = time.Now()
228 }
229 repo.Created, err = time.Parse(time.RFC3339, repoCreatedAt)
230 if err != nil {
231 repo.Created = time.Now()
232 }
233 star.Repo = &repo
234
235 stars = append(stars, star)
236 }
237
238 if err := rows.Err(); err != nil {
239 return nil, err
240 }
241
242 return stars, nil
243}