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" }}
-
+ {{ 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" }}
-
+ {{ 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" }}
-
+ {{ block "ownRepos" . }}{{ end }}
+
{{ end }}
{{ define "ownRepos" }}
-
{{ 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,
})