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

-
-
- {{ range .Timeline }} -
- {{ if .Repo }} - {{ $userHandle := index $.DidHandleMap .Repo.Did }} -
-

- {{ 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 }} -

-
- {{ else if .Follow }} - {{ $userHandle := index $.DidHandleMap .Follow.UserDid }} - {{ $subjectHandle := index $.DidHandleMap .Follow.SubjectDid }} -
-

- {{ template "user/fragments/picHandleLink" $userHandle }} - followed - {{ template "user/fragments/picHandleLink" $subjectHandle }} - {{ template "repo/fragments/time" .Follow.FollowedAt }} -

-
- {{ else if .Star }} - {{ $starrerHandle := index $.DidHandleMap .Star.StarredByDid }} - {{ $repoOwnerHandle := index $.DidHandleMap .Star.Repo.Did }} -
-

- {{ template "user/fragments/picHandleLink" $starrerHandle }} - starred - {{ $repoOwnerHandle | truncateAt30 }}/{{ .Star.Repo.Name }} - {{ template "repo/fragments/time" .Star.Created }} -

-
- {{ end }} +
+ {{ range $i, $e := .Timeline }} +
+ {{ if ne $i 0 }} +
+ {{ end }} + {{ with $e }} +
+ {{ if .Repo }} + {{ block "repoEvent" (list $ .Repo .Source) }} {{ end }} + {{ else if .Star }} + {{ block "starEvent" (list $ .Star) }} {{ end }} + {{ else if .Follow }} + {{ block "followEvent" (list $ .Follow .Profile .FollowStats) }} {{ end }} + {{ end }}
- {{ end }} + {{ end }} +
+ {{ end }}
{{ end }} + +{{ define "repoEvent" }} + {{ $root := index . 0 }} + {{ $repo := index . 1 }} + {{ $source := index . 2 }} + {{ $userHandle := index $root.DidHandleMap $repo.Did }} +
+ {{ template "user/fragments/picHandleLink" $userHandle }} + {{ with $source }} + forked + + {{ index $root.DidHandleMap .Did }}/{{ .Name }} + + to + {{ $repo.Name }} + {{ else }} + created + + {{ $repo.Name }} + + {{ end }} + {{ template "repo/fragments/time" $repo.Created }} +
+ {{ with $repo }} + {{ template "user/fragments/repoCard" (list $root . true) }} + {{ end }} +{{ end }} + +{{ define "starEvent" }} + {{ $root := index . 0 }} + {{ $star := index . 1 }} + {{ with $star }} + {{ $starrerHandle := index $root.DidHandleMap .StarredByDid }} + {{ $repoOwnerHandle := index $root.DidHandleMap .Repo.Did }} +
+ {{ template "user/fragments/picHandleLink" $starrerHandle }} + starred + + {{ $repoOwnerHandle | truncateAt30 }}/{{ .Repo.Name }} + + {{ template "repo/fragments/time" .Created }} +
+ {{ with .Repo }} + {{ template "user/fragments/repoCard" (list $root . true) }} + {{ end }} + {{ end }} +{{ end }} + + +{{ define "followEvent" }} + {{ $root := index . 0 }} + {{ $follow := index . 1 }} + {{ $profile := index . 2 }} + {{ $stat := index . 3 }} + + {{ $userHandle := index $root.DidHandleMap $follow.UserDid }} + {{ $subjectHandle := index $root.DidHandleMap $follow.SubjectDid }} +
+ {{ template "user/fragments/picHandleLink" $userHandle }} + followed + {{ template "user/fragments/picHandleLink" $subjectHandle }} + {{ template "repo/fragments/time" $follow.FollowedAt }} +
+
+
+ +
+ +
+ + {{ $subjectHandle | truncateAt30 }} + + {{ with $profile.Description }} +

{{.}}

+ {{ end }} +
+ {{ i "users" "size-4" }} + {{ $stat.Followers }} followers + + {{ $stat.Following }} following +
+
+
+{{ end }} diff --git a/appview/pages/templates/user/fragments/repoCard.html b/appview/pages/templates/user/fragments/repoCard.html index fc369fa3..cec199ba 100644 --- a/appview/pages/templates/user/fragments/repoCard.html +++ b/appview/pages/templates/user/fragments/repoCard.html @@ -5,7 +5,7 @@ {{ with $repo }}
-
+
{{- if $fullName -}} {{ index $root.DidHandleMap .Did }}/{{ .Name }} {{- else -}} @@ -26,32 +26,32 @@ {{ end }} {{ define "repoStats" }} -
- {{ with .Language }} -
-
- {{ . }} -
- {{ end }} - {{ with .StarCount }} -
- {{ i "star" "w-3 h-3 fill-current" }} - {{ . }} -
- {{ end }} - {{ with .IssueCount.Open }} -
- {{ i "circle-dot" "w-3 h-3" }} - {{ . }} -
- {{ end }} - {{ with .PullCount.Open }} -
- {{ i "git-pull-request" "w-3 h-3" }} - {{ . }} -
- {{ end }} -
+
+ {{ with .Language }} +
+
+ {{ . }} +
+ {{ end }} + {{ with .StarCount }} +
+ {{ i "star" "w-3 h-3 fill-current" }} + {{ . }} +
+ {{ end }} + {{ with .IssueCount.Open }} +
+ {{ i "circle-dot" "w-3 h-3" }} + {{ . }} +
+ {{ end }} + {{ with .PullCount.Open }} +
+ {{ i "git-pull-request" "w-3 h-3" }} + {{ . }} +
+ {{ end }} +
{{ end }} diff --git a/appview/pages/templates/user/profile.html b/appview/pages/templates/user/profile.html index fdf470ce..a157cc4c 100644 --- a/appview/pages/templates/user/profile.html +++ b/appview/pages/templates/user/profile.html @@ -8,20 +8,20 @@ {{ end }} {{ define "content" }} -
-
+
+
{{ template "user/fragments/profileCard" .Card }} {{ block "punchcard" .Punchcard }} {{ end }}
-
+
{{ block "ownRepos" . }}{{ end }} {{ block "collaboratingRepos" . }}{{ end }}
-
+
{{ block "profileTimeline" . }}{{ end }}
@@ -258,7 +258,7 @@ {{ end }}
-
+
{{ range .Repos }} {{ template "user/fragments/repoCard" (list $ . false) }} {{ else }}