diff --git a/appview/db/profile.go b/appview/db/profile.go index e880b69a..4d3a733e 100644 --- a/appview/db/profile.go +++ b/appview/db/profile.go @@ -423,7 +423,6 @@ func GetProfiles(e Execer, filters ...filter) ([]Profile, error) { } idx := idxs[did] - log.Println("idx", "idx", idx, "link", link) profileMap[did].Links[idx] = link idxs[did] = idx + 1 } diff --git a/appview/db/timeline.go b/appview/db/timeline.go index 819a3a7f..96a81238 100644 --- a/appview/db/timeline.go +++ b/appview/db/timeline.go @@ -14,66 +14,178 @@ type TimelineEvent struct { // optional: populate only if Repo is a fork Source *Repo + + // optional: populate only if event is Follow + *Profile + *FollowStats +} + +type FollowStats struct { + Followers int + Following int } +const Limit = 50 + // TODO: this gathers heterogenous events from different sources and aggregates // them in code; if we did this entirely in sql, we could order and limit and paginate easily func MakeTimeline(e Execer) ([]TimelineEvent, error) { var events []TimelineEvent - limit := 50 - repos, err := GetAllRepos(e, limit) + repos, err := getTimelineRepos(e) if err != nil { return nil, err } - follows, err := GetAllFollows(e, limit) + stars, err := getTimelineStars(e) if err != nil { return nil, err } - stars, err := GetAllStars(e, limit) + follows, err := getTimelineFollows(e) if err != nil { return nil, err } - for _, repo := range repos { - var sourceRepo *Repo - if repo.Source != "" { - sourceRepo, err = GetRepoByAtUri(e, repo.Source) - if err != nil { - return nil, err + events = append(events, repos...) + events = append(events, stars...) + events = append(events, follows...) + + sort.Slice(events, func(i, j int) bool { + return events[i].EventAt.After(events[j].EventAt) + }) + + // Limit the slice to 100 events + if len(events) > Limit { + events = events[:Limit] + } + + return events, nil +} + +func getTimelineRepos(e Execer) ([]TimelineEvent, error) { + repos, err := GetRepos(e, Limit) + if err != nil { + return nil, err + } + + // fetch all source repos + var args []string + for _, r := range repos { + if r.Source != "" { + args = append(args, r.Source) + } + } + + var origRepos []Repo + if args != nil { + origRepos, err = GetRepos(e, 0, FilterIn("at_uri", args)) + } + if err != nil { + return nil, err + } + + uriToRepo := make(map[string]Repo) + for _, r := range origRepos { + uriToRepo[r.RepoAt().String()] = r + } + + var events []TimelineEvent + for _, r := range repos { + var source *Repo + if r.Source != "" { + if origRepo, ok := uriToRepo[r.Source]; ok { + source = &origRepo } } events = append(events, TimelineEvent{ - Repo: &repo, - EventAt: repo.Created, - Source: sourceRepo, + Repo: &r, + EventAt: r.Created, + Source: source, }) } - for _, follow := range follows { - events = append(events, TimelineEvent{ - Follow: &follow, - EventAt: follow.FollowedAt, - }) + return events, nil +} + +func getTimelineStars(e Execer) ([]TimelineEvent, error) { + stars, err := GetStars(e, Limit) + if err != nil { + return nil, err + } + + // filter star records without a repo + n := 0 + for _, s := range stars { + if s.Repo != nil { + stars[n] = s + n++ + } } + stars = stars[:n] - for _, star := range stars { + var events []TimelineEvent + for _, s := range stars { events = append(events, TimelineEvent{ - Star: &star, - EventAt: star.Created, + Star: &s, + EventAt: s.Created, }) } - sort.Slice(events, func(i, j int) bool { - return events[i].EventAt.After(events[j].EventAt) - }) + return events, nil +} - // Limit the slice to 100 events - if len(events) > limit { - events = events[:limit] +func getTimelineFollows(e Execer) ([]TimelineEvent, error) { + follows, err := GetAllFollows(e, Limit) + if err != nil { + return nil, err + } + + var subjects []string + for _, f := range follows { + subjects = append(subjects, f.SubjectDid) + } + + if subjects == nil { + return nil, nil + } + + profileMap := make(map[string]Profile) + profiles, err := GetProfiles(e, FilterIn("did", subjects)) + if err != nil { + return nil, err + } + for _, p := range profiles { + profileMap[p.Did] = p + } + + followStatMap := make(map[string]FollowStats) + for _, s := range subjects { + followers, following, err := GetFollowerFollowing(e, s) + if err != nil { + return nil, err + } + followStatMap[s] = FollowStats{ + Followers: followers, + Following: following, + } + } + + var events []TimelineEvent + for _, f := range follows { + profile, ok1 := profileMap[f.SubjectDid] + followStatMap, ok2 := followStatMap[f.SubjectDid] + if !ok1 || !ok2 { + continue + } + + events = append(events, TimelineEvent{ + Follow: &f, + Profile: &profile, + FollowStats: &followStatMap, + EventAt: f.FollowedAt, + }) } return events, nil diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go index 9bfa5081..32ee76ba 100644 --- a/appview/pages/funcmap.go +++ b/appview/pages/funcmap.go @@ -241,8 +241,13 @@ func (p *Pages) funcMap() template.FuncMap { return u }, - "tinyAvatar": p.tinyAvatar, - "langColor": enry.GetColor, + "tinyAvatar": func(handle string) string { + return p.avatarUri(handle, "tiny") + }, + "fullAvatar": func(handle string) string { + return p.avatarUri(handle, "") + }, + "langColor": enry.GetColor, "layoutSide": func() string { return "col-span-1 md:col-span-2 lg:col-span-3" }, @@ -252,13 +257,19 @@ func (p *Pages) funcMap() template.FuncMap { } } -func (p *Pages) tinyAvatar(handle string) string { +func (p *Pages) avatarUri(handle, size string) string { handle = strings.TrimPrefix(handle, "@") + secret := p.avatar.SharedSecret h := hmac.New(sha256.New, []byte(secret)) h.Write([]byte(handle)) signature := hex.EncodeToString(h.Sum(nil)) - return fmt.Sprintf("%s/%s/%s?size=tiny", p.avatar.Host, signature, handle) + + sizeArg := "" + if size != "" { + sizeArg = fmt.Sprintf("size=%s", size) + } + return fmt.Sprintf("%s/%s/%s?%s", p.avatar.Host, signature, handle, sizeArg) } func icon(name string, classes []string) (template.HTML, error) { diff --git a/appview/pages/templates/timeline.html b/appview/pages/templates/timeline.html index 93708dd0..59721360 100644 --- a/appview/pages/templates/timeline.html +++ b/appview/pages/templates/timeline.html @@ -49,82 +49,109 @@
Timeline
-- {{ template "user/fragments/picHandleLink" $userHandle }} - {{ if .Source }} - forked - - {{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }} - to - {{ .Repo.Name }} - {{ else }} - created - {{ .Repo.Name }} - {{ end }} - {{ template "repo/fragments/time" .Repo.Created }} -
-- {{ template "user/fragments/picHandleLink" $userHandle }} - followed - {{ template "user/fragments/picHandleLink" $subjectHandle }} - {{ template "repo/fragments/time" .Follow.FollowedAt }} -
-- {{ template "user/fragments/picHandleLink" $starrerHandle }} - starred - {{ $repoOwnerHandle | truncateAt30 }}/{{ .Star.Repo.Name }} - {{ template "repo/fragments/time" .Star.Created }} -
-{{.}}
+ {{ end }} +