From 8d7b6bf654c238d1c87cc039d8b4c60ccc45de36 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 26 Aug 2025 20:31:36 +0100 Subject: [PATCH] appview/pages: rework followers/following/repos pages to use profile layout Change-Id: nskzsulnznwuvmqovoqzzrqzyopqsynu Signed-off-by: oppiliappan --- appview/db/repos.go | 4 +- appview/pages/pages.go | 41 +-- appview/pages/templates/user/followers.html | 20 +- appview/pages/templates/user/following.html | 20 +- appview/pages/templates/user/repos.html | 25 +- appview/pages/templates/user/starred.html | 19 ++ appview/state/profile.go | 280 ++++++++++---------- 7 files changed, 208 insertions(+), 201 deletions(-) create mode 100644 appview/pages/templates/user/starred.html diff --git a/appview/db/repos.go b/appview/db/repos.go index 14ffac38..bca27aca 100644 --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -310,9 +310,9 @@ func GetRepos(e Execer, limit int, filters ...filter) ([]Repo, error) { slices.SortFunc(repos, func(a, b Repo) int { if a.Created.After(b.Created) { - return 1 + return -1 } - return -1 + return 1 }) return repos, nil diff --git a/appview/pages/pages.go b/appview/pages/pages.go index f43d007c..5af89dde 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -444,21 +444,28 @@ func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error return p.executeProfile("user/overview", w, params) } - Profile *db.Profile +type ProfileReposParams struct { + LoggedInUser *oauth.User + Repos []db.Repo + Card *ProfileCard + Active string } -func (p *Pages) ProfileHomePage(w io.Writer, params ProfileHomePageParams) error { - return p.execute("user/profile", w, params) +func (p *Pages) ProfileRepos(w io.Writer, params ProfileReposParams) error { + params.Active = "repos" + return p.executeProfile("user/repos", w, params) } -type ReposPageParams struct { +type ProfileStarredParams struct { LoggedInUser *oauth.User Repos []db.Repo - Card ProfileCard + Card *ProfileCard + Active string } -func (p *Pages) ReposPage(w io.Writer, params ReposPageParams) error { - return p.execute("user/repos", w, params) +func (p *Pages) ProfileStarred(w io.Writer, params ProfileStarredParams) error { + params.Active = "starred" + return p.executeProfile("user/starred", w, params) } type FollowCard struct { @@ -469,24 +476,28 @@ type FollowCard struct { Profile *db.Profile } -type FollowersPageParams struct { +type ProfileFollowersParams struct { LoggedInUser *oauth.User Followers []FollowCard - Card ProfileCard + Card *ProfileCard + Active string } -func (p *Pages) FollowersPage(w io.Writer, params FollowersPageParams) error { - return p.execute("user/followers", w, params) +func (p *Pages) ProfileFollowers(w io.Writer, params ProfileFollowersParams) error { + params.Active = "overview" + return p.executeProfile("user/followers", w, params) } -type FollowingPageParams struct { +type ProfileFollowingParams struct { LoggedInUser *oauth.User Following []FollowCard - Card ProfileCard + Card *ProfileCard + Active string } -func (p *Pages) FollowingPage(w io.Writer, params FollowingPageParams) error { - return p.execute("user/following", w, params) +func (p *Pages) ProfileFollowing(w io.Writer, params ProfileFollowingParams) error { + params.Active = "overview" + return p.executeProfile("user/following", w, params) } type FollowFragmentParams struct { diff --git a/appview/pages/templates/user/followers.html b/appview/pages/templates/user/followers.html index 5950ad99..e30405cd 100644 --- a/appview/pages/templates/user/followers.html +++ b/appview/pages/templates/user/followers.html @@ -1,21 +1,9 @@ {{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · followers {{ end }} -{{ define "extrameta" }} - - - - -{{ end }} - -{{ define "content" }} -
-
- {{ template "user/fragments/profileCard" .Card }} -
-
- {{ block "followers" . }}{{ end }} -
-
+{{ define "profileContent" }} +
+ {{ block "followers" . }}{{ end }} +
{{ end }} {{ define "followers" }} diff --git a/appview/pages/templates/user/following.html b/appview/pages/templates/user/following.html index 54442232..ce77f1a5 100644 --- a/appview/pages/templates/user/following.html +++ b/appview/pages/templates/user/following.html @@ -1,21 +1,9 @@ {{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · following {{ end }} -{{ define "extrameta" }} - - - - -{{ end }} - -{{ define "content" }} -
-
- {{ template "user/fragments/profileCard" .Card }} -
-
- {{ block "following" . }}{{ end }} -
-
+{{ define "profileContent" }} +
+ {{ block "following" . }}{{ end }} +
{{ end }} {{ define "following" }} diff --git a/appview/pages/templates/user/repos.html b/appview/pages/templates/user/repos.html index 49070891..c504c700 100644 --- a/appview/pages/templates/user/repos.html +++ b/appview/pages/templates/user/repos.html @@ -1,28 +1,17 @@ {{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · repos {{ end }} -{{ define "extrameta" }} - - - - -{{ end }} - -{{ define "content" }} -
-
- {{ template "user/fragments/profileCard" .Card }} -
-
- {{ block "ownRepos" . }}{{ end }} -
-
+{{ define "profileContent" }} +
+ {{ block "ownRepos" . }}{{ end }} +
{{ end }} {{ define "ownRepos" }} -

ALL REPOSITORIES

{{ range .Repos }} - {{ template "user/fragments/repoCard" (list $ . false) }} +
+ {{ template "user/fragments/repoCard" (list $ . false) }} +
{{ else }}

This user does not have any repos yet.

{{ end }} diff --git a/appview/pages/templates/user/starred.html b/appview/pages/templates/user/starred.html new file mode 100644 index 00000000..5acca17d --- /dev/null +++ b/appview/pages/templates/user/starred.html @@ -0,0 +1,19 @@ +{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · repos {{ end }} + +{{ define "profileContent" }} +
+ {{ block "starredRepos" . }}{{ end }} +
+{{ end }} + +{{ define "starredRepos" }} +
+ {{ range .Repos }} +
+ {{ template "user/fragments/repoCard" (list $ . true) }} +
+ {{ else }} +

This user does not have any starred repos yet.

+ {{ end }} +
+{{ end }} diff --git a/appview/state/profile.go b/appview/state/profile.go index a7ff27b2..857f1bf6 100644 --- a/appview/state/profile.go +++ b/appview/state/profile.go @@ -17,55 +17,46 @@ import ( "github.com/gorilla/feeds" "tangled.sh/tangled.sh/core/api/tangled" "tangled.sh/tangled.sh/core/appview/db" - "tangled.sh/tangled.sh/core/appview/oauth" + // "tangled.sh/tangled.sh/core/appview/oauth" "tangled.sh/tangled.sh/core/appview/pages" ) func (s *State) Profile(w http.ResponseWriter, r *http.Request) { tabVal := r.URL.Query().Get("tab") switch tabVal { - case "": - s.profileHomePage(w, r) + case "", "overview": + s.profileOverview(w, r) case "repos": s.reposPage(w, r) case "followers": s.followersPage(w, r) case "following": s.followingPage(w, r) + case "starred": + s.starredPage(w, r) } } -type ProfilePageParams struct { - Id identity.Identity - LoggedInUser *oauth.User - Card pages.ProfileCard -} - -func (s *State) profilePage(w http.ResponseWriter, r *http.Request) *ProfilePageParams { +func (s *State) profile(r *http.Request) (*pages.ProfileCard, error) { didOrHandle := chi.URLParam(r, "user") if didOrHandle == "" { - http.Error(w, "bad request", http.StatusBadRequest) - return nil + return nil, fmt.Errorf("empty DID or handle") } ident, ok := r.Context().Value("resolvedId").(identity.Identity) if !ok { - log.Printf("malformed middleware") - w.WriteHeader(http.StatusInternalServerError) - return nil + return nil, fmt.Errorf("failed to resolve ID") } did := ident.DID.String() profile, err := db.GetProfile(s.db, did) if err != nil { - log.Printf("getting profile data for %s: %s", did, err) - s.pages.Error500(w) - return nil + return nil, fmt.Errorf("failed to get profile: %w", err) } followStats, err := db.GetFollowerFollowingCount(s.db, did) if err != nil { - log.Printf("getting follow stats for %s: %s", did, err) + return nil, fmt.Errorf("failed to get follower stats: %w", err) } loggedInUser := s.oauth.GetUser(r) @@ -74,160 +65,187 @@ func (s *State) profilePage(w http.ResponseWriter, r *http.Request) *ProfilePage followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did) } - return &ProfilePageParams{ - Id: ident, - LoggedInUser: loggedInUser, - Card: pages.ProfileCard{ - UserDid: did, - UserHandle: ident.Handle.String(), - Profile: profile, - FollowStatus: followStatus, - FollowersCount: followStats.Followers, - FollowingCount: followStats.Following, - }, + now := time.Now() + startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) + punchcard, err := db.MakePunchcard( + s.db, + db.FilterEq("did", did), + db.FilterGte("date", startOfYear.Format(time.DateOnly)), + db.FilterLte("date", now.Format(time.DateOnly)), + ) + if err != nil { + return nil, fmt.Errorf("failed to get punchcard for %s: %w", did, err) } + + return &pages.ProfileCard{ + UserDid: did, + UserHandle: ident.Handle.String(), + Profile: profile, + FollowStatus: followStatus, + FollowersCount: followStats.Followers, + FollowingCount: followStats.Following, + Punchcard: punchcard, + }, nil } -func (s *State) profileHomePage(w http.ResponseWriter, r *http.Request) { - pageWithProfile := s.profilePage(w, r) - if pageWithProfile == nil { +func (s *State) profileOverview(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "profileHomePage") + + profile, err := s.profile(r) + if err != nil { + l.Error("failed to build profile card", "err", err) + s.pages.Error500(w) return } + l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle) - id := pageWithProfile.Id repos, err := db.GetRepos( s.db, 0, - db.FilterEq("did", id.DID), + db.FilterEq("did", profile.UserDid), ) if err != nil { - log.Printf("getting repos for %s: %s", id.DID, err) + l.Error("failed to fetch repos", "err", err) } - profile := pageWithProfile.Card.Profile // filter out ones that are pinned pinnedRepos := []db.Repo{} for i, r := range repos { // if this is a pinned repo, add it - if slices.Contains(profile.PinnedRepos[:], r.RepoAt()) { + if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) { pinnedRepos = append(pinnedRepos, r) } // if there are no saved pins, add the first 4 repos - if profile.IsPinnedReposEmpty() && i < 4 { + if profile.Profile.IsPinnedReposEmpty() && i < 4 { pinnedRepos = append(pinnedRepos, r) } } - collaboratingRepos, err := db.CollaboratingIn(s.db, id.DID.String()) + collaboratingRepos, err := db.CollaboratingIn(s.db, profile.UserDid) if err != nil { - log.Printf("getting collaborating repos for %s: %s", id.DID, err) + l.Error("failed to fetch collaborating repos", "err", err) } pinnedCollaboratingRepos := []db.Repo{} for _, r := range collaboratingRepos { // if this is a pinned repo, add it - if slices.Contains(profile.PinnedRepos[:], r.RepoAt()) { + if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) { pinnedCollaboratingRepos = append(pinnedCollaboratingRepos, r) } } - timeline, err := db.MakeProfileTimeline(s.db, id.DID.String()) + timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid) if err != nil { - log.Printf("failed to create profile timeline for %s: %s", id.DID, err) + l.Error("failed to create timeline", "err", err) } - var didsToResolve []string - for _, r := range collaboratingRepos { - didsToResolve = append(didsToResolve, r.Did) - } - for _, byMonth := range timeline.ByMonth { - for _, pe := range byMonth.PullEvents.Items { - didsToResolve = append(didsToResolve, pe.Repo.Did) - } - for _, ie := range byMonth.IssueEvents.Items { - didsToResolve = append(didsToResolve, ie.Metadata.Repo.Did) - } - for _, re := range byMonth.RepoEvents { - didsToResolve = append(didsToResolve, re.Repo.Did) - if re.Source != nil { - didsToResolve = append(didsToResolve, re.Source.Did) - } - } + s.pages.ProfileOverview(w, pages.ProfileOverviewParams{ + LoggedInUser: s.oauth.GetUser(r), + Card: profile, + Repos: pinnedRepos, + CollaboratingRepos: pinnedCollaboratingRepos, + ProfileTimeline: timeline, + }) +} + +func (s *State) reposPage(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "reposPage") + + profile, err := s.profile(r) + if err != nil { + l.Error("failed to build profile card", "err", err) + s.pages.Error500(w) + return } + l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle) - now := time.Now() - startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) - punchcard, err := db.MakePunchcard( + repos, err := db.GetRepos( s.db, - db.FilterEq("did", id.DID), - db.FilterGte("date", startOfYear.Format(time.DateOnly)), - db.FilterLte("date", now.Format(time.DateOnly)), + 0, + db.FilterEq("did", profile.UserDid), ) if err != nil { - log.Println("failed to get punchcard for did", "did", id.DID, "err", err) + l.Error("failed to get repos", "err", err) + s.pages.Error500(w) + return } - s.pages.ProfileHomePage(w, pages.ProfileHomePageParams{ - LoggedInUser: pageWithProfile.LoggedInUser, - Repos: pinnedRepos, - CollaboratingRepos: pinnedCollaboratingRepos, - Card: pageWithProfile.Card, - Punchcard: punchcard, - ProfileTimeline: timeline, + err = s.pages.ProfileRepos(w, pages.ProfileReposParams{ + LoggedInUser: s.oauth.GetUser(r), + Repos: repos, + Card: profile, }) } -func (s *State) reposPage(w http.ResponseWriter, r *http.Request) { - pageWithProfile := s.profilePage(w, r) - if pageWithProfile == nil { +func (s *State) starredPage(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "starredPage") + + profile, err := s.profile(r) + if err != nil { + l.Error("failed to build profile card", "err", err) + s.pages.Error500(w) return } + l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle) + + stars, err := db.GetStars(s.db, 0, db.FilterEq("starred_by_did", profile.UserDid)) + if err != nil { + l.Error("failed to get stars", "err", err) + s.pages.Error500(w) + return + } + var repoAts []string + for _, s := range stars { + repoAts = append(repoAts, string(s.RepoAt)) + } - id := pageWithProfile.Id repos, err := db.GetRepos( s.db, 0, - db.FilterEq("did", id.DID), + db.FilterIn("at_uri", repoAts), ) if err != nil { - log.Printf("getting repos for %s: %s", id.DID, err) + l.Error("failed to get repos", "err", err) + s.pages.Error500(w) + return } - s.pages.ReposPage(w, pages.ReposPageParams{ - LoggedInUser: pageWithProfile.LoggedInUser, + err = s.pages.ProfileStarred(w, pages.ProfileStarredParams{ + LoggedInUser: s.oauth.GetUser(r), Repos: repos, - Card: pageWithProfile.Card, + Card: profile, }) } type FollowsPageParams struct { - LoggedInUser *oauth.User - Follows []pages.FollowCard - Card pages.ProfileCard + Follows []pages.FollowCard + Card *pages.ProfileCard } -func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows func(db.Execer, string) ([]db.Follow, error), extractDid func(db.Follow) string) (FollowsPageParams, error) { - pageWithProfile := s.profilePage(w, r) - if pageWithProfile == nil { - return FollowsPageParams{}, nil +func (s *State) followPage( + r *http.Request, + fetchFollows func(db.Execer, string) ([]db.Follow, error), + extractDid func(db.Follow) string, +) (*FollowsPageParams, error) { + l := s.logger.With("handler", "reposPage") + + profile, err := s.profile(r) + if err != nil { + return nil, err } + l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle) - id := pageWithProfile.Id - loggedInUser := pageWithProfile.LoggedInUser + loggedInUser := s.oauth.GetUser(r) - follows, err := fetchFollows(s.db, id.DID.String()) + follows, err := fetchFollows(s.db, profile.UserDid) if err != nil { - log.Printf("getting followers for %s: %s", id.DID, err) - return FollowsPageParams{}, err + l.Error("failed to fetch follows", "err", err) + return nil, err } if len(follows) == 0 { - return FollowsPageParams{ - LoggedInUser: loggedInUser, - Follows: []pages.FollowCard{}, - Card: pageWithProfile.Card, - }, nil + return nil, nil } followDids := make([]string, 0, len(follows)) @@ -237,8 +255,8 @@ func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids)) if err != nil { - log.Printf("getting profile for %s: %s", followDids, err) - return FollowsPageParams{}, err + l.Error("failed to get profiles", "followDids", followDids, "err", err) + return nil, err } followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids) @@ -246,34 +264,29 @@ func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows log.Printf("getting follow counts for %s: %s", followDids, err) } - var loggedInUserFollowing map[string]struct{} + loggedInUserFollowing := make(map[string]struct{}) if loggedInUser != nil { following, err := db.GetFollowing(s.db, loggedInUser.Did) if err != nil { - return FollowsPageParams{}, err + l.Error("failed to get follow list", "err", err, "loggedInUser", loggedInUser.Did) + return nil, err } - if len(following) > 0 { - loggedInUserFollowing = make(map[string]struct{}, len(following)) - for _, follow := range following { - loggedInUserFollowing[follow.SubjectDid] = struct{}{} - } + loggedInUserFollowing = make(map[string]struct{}, len(following)) + for _, follow := range following { + loggedInUserFollowing[follow.SubjectDid] = struct{}{} } } - followCards := make([]pages.FollowCard, 0, len(follows)) - for _, did := range followDids { - followStats, exists := followStatsMap[did] - if !exists { - followStats = db.FollowStats{} - } + followCards := make([]pages.FollowCard, len(follows)) + for i, did := range followDids { + followStats := followStatsMap[did] followStatus := db.IsNotFollowing - if loggedInUserFollowing != nil { - if _, exists := loggedInUserFollowing[did]; exists { - followStatus = db.IsFollowing - } else if loggedInUser.Did == did { - followStatus = db.IsSelf - } + if _, exists := loggedInUserFollowing[did]; exists { + followStatus = db.IsFollowing + } else if loggedInUser.Did == did { + followStatus = db.IsSelf } + var profile *db.Profile if p, exists := profiles[did]; exists { profile = p @@ -281,45 +294,44 @@ func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows profile = &db.Profile{} profile.Did = did } - followCards = append(followCards, pages.FollowCard{ + followCards[i] = pages.FollowCard{ UserDid: did, FollowStatus: followStatus, FollowersCount: followStats.Followers, FollowingCount: followStats.Following, Profile: profile, - }) + } } - return FollowsPageParams{ - LoggedInUser: loggedInUser, - Follows: followCards, - Card: pageWithProfile.Card, + return &FollowsPageParams{ + Follows: followCards, + Card: profile, }, nil } func (s *State) followersPage(w http.ResponseWriter, r *http.Request) { - followPage, err := s.followPage(w, r, db.GetFollowers, func(f db.Follow) string { return f.UserDid }) + followPage, err := s.followPage(r, db.GetFollowers, func(f db.Follow) string { return f.UserDid }) if err != nil { s.pages.Notice(w, "all-followers", "Failed to load followers") return } - s.pages.FollowersPage(w, pages.FollowersPageParams{ - LoggedInUser: followPage.LoggedInUser, + s.pages.ProfileFollowers(w, pages.ProfileFollowersParams{ + LoggedInUser: s.oauth.GetUser(r), Followers: followPage.Follows, Card: followPage.Card, }) } func (s *State) followingPage(w http.ResponseWriter, r *http.Request) { - followPage, err := s.followPage(w, r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid }) + followPage, err := s.followPage(r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid }) if err != nil { s.pages.Notice(w, "all-following", "Failed to load following") return } - s.pages.FollowingPage(w, pages.FollowingPageParams{ - LoggedInUser: followPage.LoggedInUser, + s.pages.ProfileFollowing(w, pages.ProfileFollowingParams{ + LoggedInUser: s.oauth.GetUser(r), Following: followPage.Follows, Card: followPage.Card, }) -- 2.43.0