diff --git a/appview/db/db.go b/appview/db/db.go index 6d95b932..3da2bd71 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -2465,6 +2465,17 @@ func Make(ctx context.Context, dbPath string) (*DB, error) { `) return err }) + + orm.RunMigration(conn, logger, "deduped-stars-view", func(tx *sql.Tx) error { + _, err := tx.Exec(` + create view deduped_stars as + select did, subject_type, subject, min(created) as created + from stars + group by did, subject; + `) + return err + }) + return &DB{ db, logger, diff --git a/appview/db/profile.go b/appview/db/profile.go index 3db3cb48..8219577f 100644 --- a/appview/db/profile.go +++ b/appview/db/profile.go @@ -550,7 +550,7 @@ func GetVanityStat(e Execer, did string, stat models.VanityStatKind) (uint64, er query = `select count(id) from repos where did = ?` args = append(args, did) case models.VanityStatStarCount: - 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 = ?` + query = `select count(*) from deduped_stars s join repos r on s.subject = r.repo_did where s.subject_type = 'repo' and r.did = ?` args = append(args, did) case models.VanityStatNone: return 0, nil diff --git a/appview/db/repos.go b/appview/db/repos.go index 541f02b4..705963ed 100644 --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -277,7 +277,7 @@ func GetReposPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([ // get star counts { starCountQuery := fmt.Sprintf( - `select subject, count(1) from stars where subject_type = 'repo' and subject in (%s) group by subject`, + `select subject, count(*) from deduped_stars where subject_type = 'repo' and subject in (%s) group by subject`, inClause, ) diff --git a/appview/db/star.go b/appview/db/star.go index 7ee5cf3c..3de7de92 100644 --- a/appview/db/star.go +++ b/appview/db/star.go @@ -13,7 +13,7 @@ import ( "tangled.org/core/orm" ) -func UpsertStar(e Execer, star models.Star) error { +func UpsertStar(e Execer, rkey string, star models.Star) error { _, err := e.Exec( `insert into stars (did, rkey, subject_type, subject, created) values (?, ?, ?, ?, ?) @@ -22,7 +22,7 @@ func UpsertStar(e Execer, star models.Star) error { subject = excluded.subject, created = excluded.created`, star.Did, - star.Rkey, + rkey, string(star.SubjectType), star.Subject, star.Created.Format(time.RFC3339), @@ -32,8 +32,8 @@ func UpsertStar(e Execer, star models.Star) error { func GetStars(e Execer, subject string, page pagination.Page) ([]models.Star, error) { query := ` - select did, subject_type, subject, created, rkey - from stars + select did, subject_type, subject, created + from deduped_stars where subject = ? order by created desc limit ? offset ? @@ -48,7 +48,7 @@ func GetStars(e Execer, subject string, page pagination.Page) ([]models.Star, er for rows.Next() { var star models.Star var created string - if err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created, &star.Rkey); err != nil { + if err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created); err != nil { return nil, err } @@ -97,7 +97,7 @@ func DeleteStarByRkey(e Execer, did string, rkey string) error { func GetStarCount(e Execer, subjectType models.StarSubjectType, subject string) (int, error) { stars := 0 err := e.QueryRow( - `select count(did) from stars where subject_type = ? and subject = ?`, + `select count(*) from deduped_stars where subject_type = ? and subject = ?`, string(subjectType), subject, ).Scan(&stars) if err != nil { @@ -185,8 +185,8 @@ func GetRepoStars(e Execer, page pagination.Page, filters ...orm.Filter) ([]mode } repoQuery := fmt.Sprintf( - `select did, subject_type, subject, created, rkey - from stars + `select did, subject_type, subject, created + from deduped_stars %s order by created desc %s`, @@ -203,7 +203,7 @@ func GetRepoStars(e Execer, page pagination.Page, filters ...orm.Filter) ([]mode for rows.Next() { var star models.Star var created string - err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created, &star.Rkey) + err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created) if err != nil { return nil, err } @@ -271,7 +271,7 @@ func CountStars(e Execer, filters ...orm.Filter) (int64, error) { whereClause = " where " + strings.Join(conditions, " and ") } - repoQuery := fmt.Sprintf(`select count(1) from stars %s`, whereClause) + repoQuery := fmt.Sprintf(`select count(*) from deduped_stars %s`, whereClause) var count int64 if err := e.QueryRow(repoQuery, args...).Scan(&count); err != nil { return 0, err @@ -284,25 +284,12 @@ func CountStars(e Execer, filters ...orm.Filter) (int64, error) { func GetTopStarredReposLastWeek(e Execer) ([]models.Repo, error) { // first, get the top repo DIDs by star count from the last week query := ` - with recent_starred_repos as ( - select distinct subject - from stars - where created >= datetime('now', '-7 days') - and subject_type = 'repo' - ), - repo_star_counts as ( - select - s.subject, - count(*) as stars_gained_last_week - from stars s - join recent_starred_repos rsr on s.subject = rsr.subject - where s.created >= datetime('now', '-7 days') - and s.subject_type = 'repo' - group by s.subject - ) - select rsc.subject - from repo_star_counts rsc - order by rsc.stars_gained_last_week desc + select subject + from deduped_stars + where subject_type = 'repo' + and created >= datetime('now', '-7 days') + group by subject + order by count(*) desc limit 5 ` diff --git a/appview/ingester.go b/appview/ingester.go index e23e26a0..cf2d6037 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -210,7 +210,6 @@ func (i *Ingester) ingestStar(ctx context.Context, e *jmodels.Event, l *slog.Log star := models.Star{ Did: did, - Rkey: e.Commit.RKey, Created: createdAt, } @@ -245,7 +244,7 @@ func (i *Ingester) ingestStar(ctx context.Context, e *jmodels.Event, l *slog.Log return fmt.Errorf("star record has empty subject union") } - err = db.UpsertStar(i.Db, star) + err = db.UpsertStar(i.Db, e.Commit.RKey, star) case jmodels.CommitOperationDelete: err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey) } diff --git a/appview/models/star.go b/appview/models/star.go index 0a26cf16..f36250a4 100644 --- a/appview/models/star.go +++ b/appview/models/star.go @@ -16,7 +16,6 @@ type Star struct { SubjectType StarSubjectType Subject string Created time.Time - Rkey string } // RepoStar is used for reverse mapping to repos diff --git a/appview/state/star.go b/appview/state/star.go index ffcade19..505474ea 100644 --- a/appview/state/star.go +++ b/appview/state/star.go @@ -79,11 +79,11 @@ func (s *State) Star(w http.ResponseWriter, r *http.Request) { case http.MethodPost: star := models.Star{ Did: currentUser.Did, - Rkey: tid.TID(), SubjectType: subjectType, Subject: subjectKey, Created: time.Now(), } + rkey := tid.TID() tx, err := s.db.BeginTx(r.Context(), nil) if err != nil { @@ -92,7 +92,7 @@ func (s *State) Star(w http.ResponseWriter, r *http.Request) { } defer tx.Rollback() - if err := db.UpsertStar(tx, star); err != nil { + if err := db.UpsertStar(tx, rkey, star); err != nil { l.Error("failed to star", "err", err) return } @@ -100,7 +100,7 @@ func (s *State) Star(w http.ResponseWriter, r *http.Request) { resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.FeedStarNSID, Repo: currentUser.Did, - Rkey: star.Rkey, + Rkey: rkey, Record: &lexutil.LexiconTypeDecoder{ Val: &tangled.FeedStar{ CreatedAt: star.Created.Format(time.RFC3339),