appview/{pages,db}: show star/unstar buttons on the timeline #554

merged
opened by anirudh.fi targeting master from push-qrltzqmlrlln
Changed files
+158 -13
appview
+65 -5
appview/db/follow.go
···
}
}
+
func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) {
+
if len(subjectDids) == 0 || userDid == "" {
+
return make(map[string]FollowStatus), nil
+
}
+
+
result := make(map[string]FollowStatus)
+
+
for _, subjectDid := range subjectDids {
+
if userDid == subjectDid {
+
result[subjectDid] = IsSelf
+
} else {
+
result[subjectDid] = IsNotFollowing
+
}
+
}
+
+
var querySubjects []string
+
for _, subjectDid := range subjectDids {
+
if userDid != subjectDid {
+
querySubjects = append(querySubjects, subjectDid)
+
}
+
}
+
+
if len(querySubjects) == 0 {
+
return result, nil
+
}
+
+
placeholders := make([]string, len(querySubjects))
+
args := make([]any, len(querySubjects)+1)
+
args[0] = userDid
+
+
for i, subjectDid := range querySubjects {
+
placeholders[i] = "?"
+
args[i+1] = subjectDid
+
}
+
+
query := fmt.Sprintf(`
+
SELECT subject_did
+
FROM follows
+
WHERE user_did = ? AND subject_did IN (%s)
+
`, strings.Join(placeholders, ","))
+
+
rows, err := e.Query(query, args...)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
var subjectDid string
+
if err := rows.Scan(&subjectDid); err != nil {
+
return nil, err
+
}
+
result[subjectDid] = IsFollowing
+
}
+
+
return result, nil
+
}
+
func GetFollowStatus(e Execer, userDid, subjectDid string) FollowStatus {
-
if userDid == subjectDid {
-
return IsSelf
-
} else if _, err := GetFollow(e, userDid, subjectDid); err != nil {
+
statuses, err := getFollowStatuses(e, userDid, []string{subjectDid})
+
if err != nil {
return IsNotFollowing
-
} else {
-
return IsFollowing
}
+
return statuses[subjectDid]
+
}
+
+
func GetFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) {
+
return getFollowStatuses(e, userDid, subjectDids)
}
+53 -3
appview/db/star.go
···
return stars, nil
}
+
// getStarStatuses returns a map of repo URIs to star status for a given user
+
// This is an internal helper function to avoid N+1 queries
+
func getStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) {
+
if len(repoAts) == 0 || userDid == "" {
+
return make(map[string]bool), nil
+
}
+
+
placeholders := make([]string, len(repoAts))
+
args := make([]any, len(repoAts)+1)
+
args[0] = userDid
+
+
for i, repoAt := range repoAts {
+
placeholders[i] = "?"
+
args[i+1] = repoAt.String()
+
}
+
+
query := fmt.Sprintf(`
+
SELECT repo_at
+
FROM stars
+
WHERE starred_by_did = ? AND repo_at IN (%s)
+
`, strings.Join(placeholders, ","))
+
+
rows, err := e.Query(query, args...)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
result := make(map[string]bool)
+
// Initialize all repos as not starred
+
for _, repoAt := range repoAts {
+
result[repoAt.String()] = false
+
}
+
+
// Mark starred repos as true
+
for rows.Next() {
+
var repoAt string
+
if err := rows.Scan(&repoAt); err != nil {
+
return nil, err
+
}
+
result[repoAt] = true
+
}
+
+
return result, nil
+
}
+
func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool {
-
if _, err := GetStar(e, userDid, repoAt); err != nil {
+
statuses, err := getStarStatuses(e, userDid, []syntax.ATURI{repoAt})
+
if err != nil {
return false
-
} else {
-
return true
}
+
return statuses[repoAt.String()]
}
+
// GetStarStatuses returns a map of repo URIs to star status for a given user
+
func GetStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) {
+
return getStarStatuses(e, userDid, repoAts)
+
}
func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) {
var conditions []string
var args []any
+40 -5
appview/db/timeline.go
···
import (
"sort"
"time"
+
+
"github.com/bluesky-social/indigo/atproto/syntax"
)
type TimelineEvent struct {
···
func MakeTimeline(e Execer, limit int, loggedInUserDid string) ([]TimelineEvent, error) {
var events []TimelineEvent
-
repos, err := getTimelineRepos(e, limit)
+
repos, err := getTimelineRepos(e, limit, loggedInUserDid)
if err != nil {
return nil, err
}
···
return events, nil
}
-
func getTimelineRepos(e Execer, limit int) ([]TimelineEvent, error) {
+
func getTimelineRepos(e Execer, limit int, loggedInUserDid string) ([]TimelineEvent, error) {
repos, err := GetRepos(e, limit)
if err != nil {
return nil, err
···
uriToRepo[r.RepoAt().String()] = r
}
+
var starStatuses map[string]bool
+
if loggedInUserDid != "" {
+
var repoAts []syntax.ATURI
+
for _, r := range repos {
+
repoAts = append(repoAts, r.RepoAt())
+
}
+
var err error
+
starStatuses, err = GetStarStatuses(e, loggedInUserDid, repoAts)
+
if err != nil {
+
return nil, err
+
}
+
}
+
var events []TimelineEvent
for _, r := range repos {
var source *Repo
···
}
}
+
var isStarred bool
+
if starStatuses != nil {
+
isStarred = starStatuses[r.RepoAt().String()]
+
}
+
+
var starCount int64
+
if r.RepoStats != nil {
+
starCount = int64(r.RepoStats.StarCount)
+
}
+
events = append(events, TimelineEvent{
-
Repo: &r,
-
EventAt: r.Created,
-
Source: source,
+
Repo: &r,
+
EventAt: r.Created,
+
Source: source,
+
IsStarred: isStarred,
+
StarCount: starCount,
})
}
···
return nil, err
}
+
var followStatuses map[string]FollowStatus
+
if loggedInUserDid != "" {
+
followStatuses, err = GetFollowStatuses(e, loggedInUserDid, subjects)
+
if err != nil {
+
return nil, err
+
}
+
}
+
var events []TimelineEvent
for _, f := range follows {
profile, _ := profiles[f.SubjectDid]