Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).

appview/db: split star subjects

- rename `starred_by_did` column to `did`
- rename `repo_at` column to `subject_at` and removed foreign key
constraints.
- rename `RepoAt` field to `SubjectAt`
- remove `Repo` field and split `models.Star` to `RepoStar` and
`StringStar` as now there can be two kinds of reverse mappings.

Signed-off-by: Seongmin Lee <git@boltless.me>

authored by boltless.me and committed by

Tangled fe343b6c 60985c47

+118 -139
+39 -2
appview/db/db.go
··· 569 569 -- indexes for better performance 570 570 create index if not exists idx_notifications_recipient_created on notifications(recipient_did, created desc); 571 571 create index if not exists idx_notifications_recipient_read on notifications(recipient_did, read); 572 - create index if not exists idx_stars_created on stars(created); 573 - create index if not exists idx_stars_repo_at_created on stars(repo_at, created); 574 572 `) 575 573 if err != nil { 576 574 return nil, err ··· 1122 1124 runMigration(conn, logger, "add-usermentioned-preference", func(tx *sql.Tx) error { 1123 1125 _, err := tx.Exec(` 1124 1126 alter table notification_preferences add column user_mentioned integer not null default 1; 1127 + `) 1128 + return err 1129 + }) 1130 + 1131 + // remove the foreign key constraints from stars. 1132 + runMigration(conn, logger, "generalize-stars-subject", func(tx *sql.Tx) error { 1133 + _, err := tx.Exec(` 1134 + create table stars_new ( 1135 + id integer primary key autoincrement, 1136 + did text not null, 1137 + rkey text not null, 1138 + 1139 + subject_at text not null, 1140 + 1141 + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1142 + unique(did, rkey), 1143 + unique(did, subject_at) 1144 + ); 1145 + 1146 + insert into stars_new ( 1147 + id, 1148 + did, 1149 + rkey, 1150 + subject_at, 1151 + created 1152 + ) 1153 + select 1154 + id, 1155 + starred_by_did, 1156 + rkey, 1157 + repo_at, 1158 + created 1159 + from stars; 1160 + 1161 + drop table stars; 1162 + alter table stars_new rename to stars; 1163 + 1164 + create index if not exists idx_stars_created on stars(created); 1165 + create index if not exists idx_stars_subject_at_created on stars(subject_at, created); 1125 1166 `) 1126 1167 return err 1127 1168 })
+3 -3
appview/db/repos.go
··· 208 208 209 209 starCountQuery := fmt.Sprintf( 210 210 `select 211 - repo_at, count(1) 211 + subject_at, count(1) 212 212 from stars 213 - where repo_at in (%s) 214 - group by repo_at`, 213 + where subject_at in (%s) 214 + group by subject_at`, 215 215 inClause, 216 216 ) 217 217 rows, err = e.Query(starCountQuery, args...)
+39 -99
appview/db/star.go
··· 14 14 ) 15 15 16 16 func AddStar(e Execer, star *models.Star) error { 17 - query := `insert or ignore into stars (starred_by_did, repo_at, rkey) values (?, ?, ?)` 17 + query := `insert or ignore into stars (did, subject_at, rkey) values (?, ?, ?)` 18 18 _, err := e.Exec( 19 19 query, 20 - star.StarredByDid, 20 + star.Did, 21 21 star.RepoAt.String(), 22 22 star.Rkey, 23 23 ) ··· 25 25 } 26 26 27 27 // Get a star record 28 - func GetStar(e Execer, starredByDid string, repoAt syntax.ATURI) (*models.Star, error) { 28 + func GetStar(e Execer, did string, subjectAt syntax.ATURI) (*models.Star, error) { 29 29 query := ` 30 - select starred_by_did, repo_at, created, rkey 30 + select did, subject_at, created, rkey 31 31 from stars 32 - where starred_by_did = ? and repo_at = ?` 33 - row := e.QueryRow(query, starredByDid, repoAt) 32 + where did = ? and subject_at = ?` 33 + row := e.QueryRow(query, did, subjectAt) 34 34 35 35 var star models.Star 36 36 var created string 37 - err := row.Scan(&star.StarredByDid, &star.RepoAt, &created, &star.Rkey) 37 + err := row.Scan(&star.Did, &star.RepoAt, &created, &star.Rkey) 38 38 if err != nil { 39 39 return nil, err 40 40 } ··· 51 51 } 52 52 53 53 // Remove a star 54 - func DeleteStar(e Execer, starredByDid string, repoAt syntax.ATURI) error { 55 - _, err := e.Exec(`delete from stars where starred_by_did = ? and repo_at = ?`, starredByDid, repoAt) 54 + func DeleteStar(e Execer, did string, subjectAt syntax.ATURI) error { 55 + _, err := e.Exec(`delete from stars where did = ? and subject_at = ?`, did, subjectAt) 56 56 return err 57 57 } 58 58 59 59 // Remove a star 60 - func DeleteStarByRkey(e Execer, starredByDid string, rkey string) error { 61 - _, err := e.Exec(`delete from stars where starred_by_did = ? and rkey = ?`, starredByDid, rkey) 60 + func DeleteStarByRkey(e Execer, did string, rkey string) error { 61 + _, err := e.Exec(`delete from stars where did = ? and rkey = ?`, did, rkey) 62 62 return err 63 63 } 64 64 65 - func GetStarCount(e Execer, repoAt syntax.ATURI) (int, error) { 65 + func GetStarCount(e Execer, subjectAt syntax.ATURI) (int, error) { 66 66 stars := 0 67 67 err := e.QueryRow( 68 - `select count(starred_by_did) from stars where repo_at = ?`, repoAt).Scan(&stars) 68 + `select count(did) from stars where subject_at = ?`, subjectAt).Scan(&stars) 69 69 if err != nil { 70 70 return 0, err 71 71 } ··· 89 89 } 90 90 91 91 query := fmt.Sprintf(` 92 - SELECT repo_at 92 + SELECT subject_at 93 93 FROM stars 94 - WHERE starred_by_did = ? AND repo_at IN (%s) 94 + WHERE did = ? AND subject_at IN (%s) 95 95 `, strings.Join(placeholders, ",")) 96 96 97 97 rows, err := e.Query(query, args...) ··· 118 118 return result, nil 119 119 } 120 120 121 - func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool { 122 - statuses, err := getStarStatuses(e, userDid, []syntax.ATURI{repoAt}) 121 + func GetStarStatus(e Execer, userDid string, subjectAt syntax.ATURI) bool { 122 + statuses, err := getStarStatuses(e, userDid, []syntax.ATURI{subjectAt}) 123 123 if err != nil { 124 124 return false 125 125 } 126 - return statuses[repoAt.String()] 126 + return statuses[subjectAt.String()] 127 127 } 128 128 129 129 // GetStarStatuses returns a map of repo URIs to star status for a given user 130 - func GetStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) { 131 - return getStarStatuses(e, userDid, repoAts) 130 + func GetStarStatuses(e Execer, userDid string, subjectAts []syntax.ATURI) (map[string]bool, error) { 131 + return getStarStatuses(e, userDid, subjectAts) 132 132 } 133 - func GetStars(e Execer, limit int, filters ...filter) ([]models.Star, error) { 133 + 134 + // GetRepoStars return a list of stars each holding target repository. 135 + // If there isn't known repo with starred at-uri, those stars will be ignored. 136 + func GetRepoStars(e Execer, limit int, filters ...filter) ([]models.RepoStar, error) { 134 137 var conditions []string 135 138 var args []any 136 139 for _, filter := range filters { ··· 152 149 } 153 150 154 151 repoQuery := fmt.Sprintf( 155 - `select starred_by_did, repo_at, created, rkey 152 + `select did, subject_at, created, rkey 156 153 from stars 157 154 %s 158 155 order by created desc ··· 169 166 for rows.Next() { 170 167 var star models.Star 171 168 var created string 172 - err := rows.Scan(&star.StarredByDid, &star.RepoAt, &created, &star.Rkey) 169 + err := rows.Scan(&star.Did, &star.RepoAt, &created, &star.Rkey) 173 170 if err != nil { 174 171 return nil, err 175 172 } ··· 200 197 return nil, err 201 198 } 202 199 200 + var repoStars []models.RepoStar 203 201 for _, r := range repos { 204 202 if stars, ok := starMap[string(r.RepoAt())]; ok { 205 - for i := range stars { 206 - stars[i].Repo = &r 203 + for _, star := range stars { 204 + repoStars = append(repoStars, models.RepoStar{ 205 + Star: star, 206 + Repo: &r, 207 + }) 207 208 } 208 209 } 209 210 } 210 211 211 - var stars []models.Star 212 - for _, s := range starMap { 213 - stars = append(stars, s...) 214 - } 215 - 216 - slices.SortFunc(stars, func(a, b models.Star) int { 212 + slices.SortFunc(repoStars, func(a, b models.RepoStar) int { 217 213 if a.Created.After(b.Created) { 218 214 return -1 219 215 } ··· 222 220 return 0 223 221 }) 224 222 225 - return stars, nil 223 + return repoStars, nil 226 224 } 227 225 228 226 func CountStars(e Execer, filters ...filter) (int64, error) { ··· 249 247 return count, nil 250 248 } 251 249 252 - func GetAllStars(e Execer, limit int) ([]models.Star, error) { 253 - var stars []models.Star 254 - 255 - rows, err := e.Query(` 256 - select 257 - s.starred_by_did, 258 - s.repo_at, 259 - s.rkey, 260 - s.created, 261 - r.did, 262 - r.name, 263 - r.knot, 264 - r.rkey, 265 - r.created 266 - from stars s 267 - join repos r on s.repo_at = r.at_uri 268 - `) 269 - 270 - if err != nil { 271 - return nil, err 272 - } 273 - defer rows.Close() 274 - 275 - for rows.Next() { 276 - var star models.Star 277 - var repo models.Repo 278 - var starCreatedAt, repoCreatedAt string 279 - 280 - if err := rows.Scan( 281 - &star.StarredByDid, 282 - &star.RepoAt, 283 - &star.Rkey, 284 - &starCreatedAt, 285 - &repo.Did, 286 - &repo.Name, 287 - &repo.Knot, 288 - &repo.Rkey, 289 - &repoCreatedAt, 290 - ); err != nil { 291 - return nil, err 292 - } 293 - 294 - star.Created, err = time.Parse(time.RFC3339, starCreatedAt) 295 - if err != nil { 296 - star.Created = time.Now() 297 - } 298 - repo.Created, err = time.Parse(time.RFC3339, repoCreatedAt) 299 - if err != nil { 300 - repo.Created = time.Now() 301 - } 302 - star.Repo = &repo 303 - 304 - stars = append(stars, star) 305 - } 306 - 307 - if err := rows.Err(); err != nil { 308 - return nil, err 309 - } 310 - 311 - return stars, nil 312 - } 313 - 314 250 // GetTopStarredReposLastWeek returns the top 8 most starred repositories from the last week 315 251 func GetTopStarredReposLastWeek(e Execer) ([]models.Repo, error) { 316 252 // first, get the top repo URIs by star count from the last week 317 253 query := ` 318 254 with recent_starred_repos as ( 319 - select distinct repo_at 255 + select distinct subject_at 320 256 from stars 321 257 where created >= datetime('now', '-7 days') 322 258 ), 323 259 repo_star_counts as ( 324 260 select 325 - s.repo_at, 261 + s.subject_at, 326 262 count(*) as stars_gained_last_week 327 263 from stars s 328 - join recent_starred_repos rsr on s.repo_at = rsr.repo_at 264 + join recent_starred_repos rsr on s.subject_at = rsr.subject_at 329 265 where s.created >= datetime('now', '-7 days') 330 - group by s.repo_at 266 + group by s.subject_at 331 267 ) 332 - select rsc.repo_at 268 + select rsc.subject_at 333 269 from repo_star_counts rsc 334 270 order by rsc.stars_gained_last_week desc 335 271 limit 8
+3 -13
appview/db/timeline.go
··· 146 146 func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 147 147 filters := make([]filter, 0) 148 148 if userIsFollowing != nil { 149 - filters = append(filters, FilterIn("starred_by_did", userIsFollowing)) 149 + filters = append(filters, FilterIn("did", userIsFollowing)) 150 150 } 151 151 152 - stars, err := GetStars(e, limit, filters...) 152 + stars, err := GetRepoStars(e, limit, filters...) 153 153 if err != nil { 154 154 return nil, err 155 155 } 156 - 157 - // filter star records without a repo 158 - n := 0 159 - for _, s := range stars { 160 - if s.Repo != nil { 161 - stars[n] = s 162 - n++ 163 - } 164 - } 165 - stars = stars[:n] 166 156 167 157 var repos []models.Repo 168 158 for _, s := range stars { ··· 169 179 isStarred, starCount := getRepoStarInfo(s.Repo, starStatuses) 170 180 171 181 events = append(events, models.TimelineEvent{ 172 - Star: &s, 182 + RepoStar: &s, 173 183 EventAt: s.Created, 174 184 IsStarred: isStarred, 175 185 StarCount: starCount,
+3 -3
appview/ingester.go
··· 121 121 return err 122 122 } 123 123 err = db.AddStar(i.Db, &models.Star{ 124 - StarredByDid: did, 125 - RepoAt: subjectUri, 126 - Rkey: e.Commit.RKey, 124 + Did: did, 125 + RepoAt: subjectUri, 126 + Rkey: e.Commit.RKey, 127 127 }) 128 128 case jmodels.CommitOperationDelete: 129 129 err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey)
+14 -5
appview/models/star.go
··· 7 7 ) 8 8 9 9 type Star struct { 10 - StarredByDid string 11 - RepoAt syntax.ATURI 12 - Created time.Time 13 - Rkey string 10 + Did string 11 + RepoAt syntax.ATURI 12 + Created time.Time 13 + Rkey string 14 + } 14 15 15 - // optionally, populate this when querying for reverse mappings 16 + // RepoStar is used for reverse mapping to repos 17 + type RepoStar struct { 18 + Star 16 19 Repo *Repo 20 + } 21 + 22 + // StringStar is used for reverse mapping to strings 23 + type StringStar struct { 24 + Star 25 + String *String 17 26 }
+1 -1
appview/models/timeline.go
··· 5 5 type TimelineEvent struct { 6 6 *Repo 7 7 *Follow 8 - *Star 8 + *RepoStar 9 9 10 10 EventAt time.Time 11 11
+6 -1
appview/notify/db/db.go
··· 7 7 "slices" 8 8 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 + "tangled.org/core/api/tangled" 10 11 "tangled.org/core/appview/db" 11 12 "tangled.org/core/appview/models" 12 13 "tangled.org/core/appview/notify" ··· 37 36 } 38 37 39 38 func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) { 39 + if star.RepoAt.Collection().String() != tangled.RepoNSID { 40 + // skip string stars for now 41 + return 42 + } 40 43 var err error 41 44 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt))) 42 45 if err != nil { ··· 48 43 return 49 44 } 50 45 51 - actorDid := syntax.DID(star.StarredByDid) 46 + actorDid := syntax.DID(star.Did) 52 47 recipients := []syntax.DID{syntax.DID(repo.Did)} 53 48 eventType := models.NotificationTypeRepoStarred 54 49 entityType := "repo"
+2 -2
appview/notify/posthog/notifier.go
··· 37 37 38 38 func (n *posthogNotifier) NewStar(ctx context.Context, star *models.Star) { 39 39 err := n.client.Enqueue(posthog.Capture{ 40 - DistinctId: star.StarredByDid, 40 + DistinctId: star.Did, 41 41 Event: "star", 42 42 Properties: posthog.Properties{"repo_at": star.RepoAt.String()}, 43 43 }) ··· 48 48 49 49 func (n *posthogNotifier) DeleteStar(ctx context.Context, star *models.Star) { 50 50 err := n.client.Enqueue(posthog.Capture{ 51 - DistinctId: star.StarredByDid, 51 + DistinctId: star.Did, 52 52 Event: "unstar", 53 53 Properties: posthog.Properties{"repo_at": star.RepoAt.String()}, 54 54 })
+2 -2
appview/pages/templates/timeline/fragments/timeline.html
··· 59 59 {{ define "timeline/fragments/starEvent" }} 60 60 {{ $root := index . 0 }} 61 61 {{ $event := index . 1 }} 62 - {{ $star := $event.Star }} 62 + {{ $star := $event.RepoStar }} 63 63 {{ with $star }} 64 - {{ $starrerHandle := resolve .StarredByDid }} 64 + {{ $starrerHandle := resolve .Did }} 65 65 {{ $repoOwnerHandle := resolve .Repo.Did }} 66 66 <div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm"> 67 67 {{ template "user/fragments/picHandleLink" $starrerHandle }}
+3 -5
appview/state/profile.go
··· 66 66 return nil, fmt.Errorf("failed to get string count: %w", err) 67 67 } 68 68 69 - starredCount, err := db.CountStars(s.db, db.FilterEq("starred_by_did", did)) 69 + starredCount, err := db.CountStars(s.db, db.FilterEq("did", did)) 70 70 if err != nil { 71 71 return nil, fmt.Errorf("failed to get starred repo count: %w", err) 72 72 } ··· 211 211 } 212 212 l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle) 213 213 214 - stars, err := db.GetStars(s.db, 0, db.FilterEq("starred_by_did", profile.UserDid)) 214 + stars, err := db.GetRepoStars(s.db, 0, db.FilterEq("did", profile.UserDid)) 215 215 if err != nil { 216 216 l.Error("failed to get stars", "err", err) 217 217 s.pages.Error500(w) ··· 219 219 } 220 220 var repos []models.Repo 221 221 for _, s := range stars { 222 - if s.Repo != nil { 223 - repos = append(repos, *s.Repo) 224 - } 222 + repos = append(repos, *s.Repo) 225 223 } 226 224 227 225 err = s.pages.ProfileStarred(w, pages.ProfileStarredParams{
+3 -3
appview/state/star.go
··· 57 57 log.Println("created atproto record: ", resp.Uri) 58 58 59 59 star := &models.Star{ 60 - StarredByDid: currentUser.Did, 61 - RepoAt: subjectUri, 62 - Rkey: rkey, 60 + Did: currentUser.Did, 61 + RepoAt: subjectUri, 62 + Rkey: rkey, 63 63 } 64 64 65 65 err = db.AddStar(s.db, star)