Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

appview: deduplicate stars

Similar to follow records from previous commit, deduplicate stars.
Define a `deduped_stars` view to query deduplicated stars. Oldest star
wins so refreshing stars won't put repository to trending list.

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

author
Seongmin Lee
committer
Tangled
date (Jul 22, 2026, 5:50 PM +0300) commit 94502362 parent 67a7aa29 change-id loxtmwyy
+33 -37
+11
appview/db/db.go
··· 2465 2465 `) 2466 2466 return err 2467 2467 }) 2468 + 2469 + orm.RunMigration(conn, logger, "deduped-stars-view", func(tx *sql.Tx) error { 2470 + _, err := tx.Exec(` 2471 + create view deduped_stars as 2472 + select did, subject_type, subject, min(created) as created 2473 + from stars 2474 + group by did, subject; 2475 + `) 2476 + return err 2477 + }) 2478 + 2468 2479 return &DB{ 2469 2480 db, 2470 2481 logger,
+1 -1
appview/db/profile.go
··· 550 550 query = `select count(id) from repos where did = ?` 551 551 args = append(args, did) 552 552 case models.VanityStatStarCount: 553 - query = `select count(s.at_uri) from stars s join repos r on s.subject = r.repo_did where s.subject_type = 'repo' and r.did = ?` 553 + query = `select count(*) from deduped_stars s join repos r on s.subject = r.repo_did where s.subject_type = 'repo' and r.did = ?` 554 554 args = append(args, did) 555 555 case models.VanityStatNone: 556 556 return 0, nil
+1 -1
appview/db/repos.go
··· 277 277 // get star counts 278 278 { 279 279 starCountQuery := fmt.Sprintf( 280 - `select subject, count(1) from stars where subject_type = 'repo' and subject in (%s) group by subject`, 280 + `select subject, count(*) from deduped_stars where subject_type = 'repo' and subject in (%s) group by subject`, 281 281 inClause, 282 282 ) 283 283
+16 -29
appview/db/star.go
··· 13 13 "tangled.org/core/orm" 14 14 ) 15 15 16 - func UpsertStar(e Execer, star models.Star) error { 16 + func UpsertStar(e Execer, rkey string, star models.Star) error { 17 17 _, err := e.Exec( 18 18 `insert into stars (did, rkey, subject_type, subject, created) 19 19 values (?, ?, ?, ?, ?) ··· 22 22 subject = excluded.subject, 23 23 created = excluded.created`, 24 24 star.Did, 25 - star.Rkey, 25 + rkey, 26 26 string(star.SubjectType), 27 27 star.Subject, 28 28 star.Created.Format(time.RFC3339), ··· 32 32 33 33 func GetStars(e Execer, subject string, page pagination.Page) ([]models.Star, error) { 34 34 query := ` 35 - select did, subject_type, subject, created, rkey 36 - from stars 35 + select did, subject_type, subject, created 36 + from deduped_stars 37 37 where subject = ? 38 38 order by created desc 39 39 limit ? offset ? ··· 48 48 for rows.Next() { 49 49 var star models.Star 50 50 var created string 51 - if err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created, &star.Rkey); err != nil { 51 + if err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created); err != nil { 52 52 return nil, err 53 53 } 54 54 ··· 97 97 func GetStarCount(e Execer, subjectType models.StarSubjectType, subject string) (int, error) { 98 98 stars := 0 99 99 err := e.QueryRow( 100 - `select count(did) from stars where subject_type = ? and subject = ?`, 100 + `select count(*) from deduped_stars where subject_type = ? and subject = ?`, 101 101 string(subjectType), subject, 102 102 ).Scan(&stars) 103 103 if err != nil { ··· 185 185 } 186 186 187 187 repoQuery := fmt.Sprintf( 188 - `select did, subject_type, subject, created, rkey 189 - from stars 188 + `select did, subject_type, subject, created 189 + from deduped_stars 190 190 %s 191 191 order by created desc 192 192 %s`, ··· 203 203 for rows.Next() { 204 204 var star models.Star 205 205 var created string 206 - err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created, &star.Rkey) 206 + err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created) 207 207 if err != nil { 208 208 return nil, err 209 209 } ··· 271 271 whereClause = " where " + strings.Join(conditions, " and ") 272 272 } 273 273 274 - repoQuery := fmt.Sprintf(`select count(1) from stars %s`, whereClause) 274 + repoQuery := fmt.Sprintf(`select count(*) from deduped_stars %s`, whereClause) 275 275 var count int64 276 276 if err := e.QueryRow(repoQuery, args...).Scan(&count); err != nil { 277 277 return 0, err ··· 284 284 func GetTopStarredReposLastWeek(e Execer) ([]models.Repo, error) { 285 285 // first, get the top repo DIDs by star count from the last week 286 286 query := ` 287 - with recent_starred_repos as ( 288 - select distinct subject 289 - from stars 290 - where created >= datetime('now', '-7 days') 291 - and subject_type = 'repo' 292 - ), 293 - repo_star_counts as ( 294 - select 295 - s.subject, 296 - count(*) as stars_gained_last_week 297 - from stars s 298 - join recent_starred_repos rsr on s.subject = rsr.subject 299 - where s.created >= datetime('now', '-7 days') 300 - and s.subject_type = 'repo' 301 - group by s.subject 302 - ) 303 - select rsc.subject 304 - from repo_star_counts rsc 305 - order by rsc.stars_gained_last_week desc 287 + select subject 288 + from deduped_stars 289 + where subject_type = 'repo' 290 + and created >= datetime('now', '-7 days') 291 + group by subject 292 + order by count(*) desc 306 293 limit 5 307 294 ` 308 295
+1 -2
appview/ingester.go
··· 210 210 211 211 star := models.Star{ 212 212 Did: did, 213 - Rkey: e.Commit.RKey, 214 213 Created: createdAt, 215 214 } 216 215 ··· 245 244 return fmt.Errorf("star record has empty subject union") 246 245 } 247 246 248 - err = db.UpsertStar(i.Db, star) 247 + err = db.UpsertStar(i.Db, e.Commit.RKey, star) 249 248 case jmodels.CommitOperationDelete: 250 249 err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey) 251 250 }
-1
appview/models/star.go
··· 16 16 SubjectType StarSubjectType 17 17 Subject string 18 18 Created time.Time 19 - Rkey string 20 19 } 21 20 22 21 // RepoStar is used for reverse mapping to repos
+3 -3
appview/state/star.go
··· 79 79 case http.MethodPost: 80 80 star := models.Star{ 81 81 Did: currentUser.Did, 82 - Rkey: tid.TID(), 83 82 SubjectType: subjectType, 84 83 Subject: subjectKey, 85 84 Created: time.Now(), 86 85 } 86 + rkey := tid.TID() 87 87 88 88 tx, err := s.db.BeginTx(r.Context(), nil) 89 89 if err != nil { ··· 92 92 } 93 93 defer tx.Rollback() 94 94 95 - if err := db.UpsertStar(tx, star); err != nil { 95 + if err := db.UpsertStar(tx, rkey, star); err != nil { 96 96 l.Error("failed to star", "err", err) 97 97 return 98 98 } ··· 100 100 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 101 101 Collection: tangled.FeedStarNSID, 102 102 Repo: currentUser.Did, 103 - Rkey: star.Rkey, 103 + Rkey: rkey, 104 104 Record: &lexutil.LexiconTypeDecoder{ 105 105 Val: &tangled.FeedStar{ 106 106 CreatedAt: star.Created.Format(time.RFC3339),