Signed-off-by: oppiliappan me@oppi.li
+2
-2
appview/db/repos.go
+2
-2
appview/db/repos.go
+26
-15
appview/pages/pages.go
+26
-15
appview/pages/pages.go
···
444
return p.executeProfile("user/overview", w, params)
445
}
446
447
-
Profile *db.Profile
448
}
449
450
-
func (p *Pages) ProfileHomePage(w io.Writer, params ProfileHomePageParams) error {
451
-
return p.execute("user/profile", w, params)
452
}
453
454
-
type ReposPageParams struct {
455
LoggedInUser *oauth.User
456
Repos []db.Repo
457
-
Card ProfileCard
458
}
459
460
-
func (p *Pages) ReposPage(w io.Writer, params ReposPageParams) error {
461
-
return p.execute("user/repos", w, params)
462
}
463
464
type FollowCard struct {
···
469
Profile *db.Profile
470
}
471
472
-
type FollowersPageParams struct {
473
LoggedInUser *oauth.User
474
Followers []FollowCard
475
-
Card ProfileCard
476
}
477
478
-
func (p *Pages) FollowersPage(w io.Writer, params FollowersPageParams) error {
479
-
return p.execute("user/followers", w, params)
480
}
481
482
-
type FollowingPageParams struct {
483
LoggedInUser *oauth.User
484
Following []FollowCard
485
-
Card ProfileCard
486
}
487
488
-
func (p *Pages) FollowingPage(w io.Writer, params FollowingPageParams) error {
489
-
return p.execute("user/following", w, params)
490
}
491
492
type FollowFragmentParams struct {
···
444
return p.executeProfile("user/overview", w, params)
445
}
446
447
+
type ProfileReposParams struct {
448
+
LoggedInUser *oauth.User
449
+
Repos []db.Repo
450
+
Card *ProfileCard
451
+
Active string
452
}
453
454
+
func (p *Pages) ProfileRepos(w io.Writer, params ProfileReposParams) error {
455
+
params.Active = "repos"
456
+
return p.executeProfile("user/repos", w, params)
457
}
458
459
+
type ProfileStarredParams struct {
460
LoggedInUser *oauth.User
461
Repos []db.Repo
462
+
Card *ProfileCard
463
+
Active string
464
}
465
466
+
func (p *Pages) ProfileStarred(w io.Writer, params ProfileStarredParams) error {
467
+
params.Active = "starred"
468
+
return p.executeProfile("user/starred", w, params)
469
}
470
471
type FollowCard struct {
···
476
Profile *db.Profile
477
}
478
479
+
type ProfileFollowersParams struct {
480
LoggedInUser *oauth.User
481
Followers []FollowCard
482
+
Card *ProfileCard
483
+
Active string
484
}
485
486
+
func (p *Pages) ProfileFollowers(w io.Writer, params ProfileFollowersParams) error {
487
+
params.Active = "overview"
488
+
return p.executeProfile("user/followers", w, params)
489
}
490
491
+
type ProfileFollowingParams struct {
492
LoggedInUser *oauth.User
493
Following []FollowCard
494
+
Card *ProfileCard
495
+
Active string
496
}
497
498
+
func (p *Pages) ProfileFollowing(w io.Writer, params ProfileFollowingParams) error {
499
+
params.Active = "overview"
500
+
return p.executeProfile("user/following", w, params)
501
}
502
503
type FollowFragmentParams struct {
+4
-16
appview/pages/templates/user/followers.html
+4
-16
appview/pages/templates/user/followers.html
···
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · followers {{ end }}
2
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}'s followers" />
5
-
<meta property="og:type" content="object" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab=followers" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
{{ template "user/fragments/profileCard" .Card }}
14
-
</div>
15
-
<div id="all-followers" class="md:col-span-8 order-2 md:order-2">
16
-
{{ block "followers" . }}{{ end }}
17
-
</div>
18
-
</div>
19
{{ end }}
20
21
{{ define "followers" }}
+4
-16
appview/pages/templates/user/following.html
+4
-16
appview/pages/templates/user/following.html
···
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · following {{ end }}
2
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}'s following" />
5
-
<meta property="og:type" content="object" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab=following" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
{{ template "user/fragments/profileCard" .Card }}
14
-
</div>
15
-
<div id="all-following" class="md:col-span-8 order-2 md:order-2">
16
-
{{ block "following" . }}{{ end }}
17
-
</div>
18
-
</div>
19
{{ end }}
20
21
{{ define "following" }}
+7
-18
appview/pages/templates/user/repos.html
+7
-18
appview/pages/templates/user/repos.html
···
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · repos {{ end }}
2
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}'s repos" />
5
-
<meta property="og:type" content="object" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab=repos" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
{{ template "user/fragments/profileCard" .Card }}
14
-
</div>
15
-
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
16
-
{{ block "ownRepos" . }}{{ end }}
17
-
</div>
18
-
</div>
19
{{ end }}
20
21
{{ define "ownRepos" }}
22
-
<p class="text-sm font-bold p-2 dark:text-white">ALL REPOSITORIES</p>
23
<div id="repos" class="grid grid-cols-1 gap-4 mb-6">
24
{{ range .Repos }}
25
-
{{ template "user/fragments/repoCard" (list $ . false) }}
26
{{ else }}
27
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
28
{{ end }}
···
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · repos {{ end }}
2
3
+
{{ define "profileContent" }}
4
+
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
5
+
{{ block "ownRepos" . }}{{ end }}
6
+
</div>
7
{{ end }}
8
9
{{ define "ownRepos" }}
10
<div id="repos" class="grid grid-cols-1 gap-4 mb-6">
11
{{ range .Repos }}
12
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
13
+
{{ template "user/fragments/repoCard" (list $ . false) }}
14
+
</div>
15
{{ else }}
16
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
17
{{ end }}
+19
appview/pages/templates/user/starred.html
+19
appview/pages/templates/user/starred.html
···
···
1
+
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · repos {{ end }}
2
+
3
+
{{ define "profileContent" }}
4
+
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
5
+
{{ block "starredRepos" . }}{{ end }}
6
+
</div>
7
+
{{ end }}
8
+
9
+
{{ define "starredRepos" }}
10
+
<div id="repos" class="grid grid-cols-1 gap-4 mb-6">
11
+
{{ range .Repos }}
12
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
13
+
{{ template "user/fragments/repoCard" (list $ . true) }}
14
+
</div>
15
+
{{ else }}
16
+
<p class="px-6 dark:text-white">This user does not have any starred repos yet.</p>
17
+
{{ end }}
18
+
</div>
19
+
{{ end }}
+146
-134
appview/state/profile.go
+146
-134
appview/state/profile.go
···
17
"github.com/gorilla/feeds"
18
"tangled.sh/tangled.sh/core/api/tangled"
19
"tangled.sh/tangled.sh/core/appview/db"
20
-
"tangled.sh/tangled.sh/core/appview/oauth"
21
"tangled.sh/tangled.sh/core/appview/pages"
22
)
23
24
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
25
tabVal := r.URL.Query().Get("tab")
26
switch tabVal {
27
-
case "":
28
-
s.profileHomePage(w, r)
29
case "repos":
30
s.reposPage(w, r)
31
case "followers":
32
s.followersPage(w, r)
33
case "following":
34
s.followingPage(w, r)
35
}
36
}
37
38
-
type ProfilePageParams struct {
39
-
Id identity.Identity
40
-
LoggedInUser *oauth.User
41
-
Card pages.ProfileCard
42
-
}
43
-
44
-
func (s *State) profilePage(w http.ResponseWriter, r *http.Request) *ProfilePageParams {
45
didOrHandle := chi.URLParam(r, "user")
46
if didOrHandle == "" {
47
-
http.Error(w, "bad request", http.StatusBadRequest)
48
-
return nil
49
}
50
51
ident, ok := r.Context().Value("resolvedId").(identity.Identity)
52
if !ok {
53
-
log.Printf("malformed middleware")
54
-
w.WriteHeader(http.StatusInternalServerError)
55
-
return nil
56
}
57
did := ident.DID.String()
58
59
profile, err := db.GetProfile(s.db, did)
60
if err != nil {
61
-
log.Printf("getting profile data for %s: %s", did, err)
62
-
s.pages.Error500(w)
63
-
return nil
64
}
65
66
followStats, err := db.GetFollowerFollowingCount(s.db, did)
67
if err != nil {
68
-
log.Printf("getting follow stats for %s: %s", did, err)
69
}
70
71
loggedInUser := s.oauth.GetUser(r)
···
74
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did)
75
}
76
77
-
return &ProfilePageParams{
78
-
Id: ident,
79
-
LoggedInUser: loggedInUser,
80
-
Card: pages.ProfileCard{
81
-
UserDid: did,
82
-
UserHandle: ident.Handle.String(),
83
-
Profile: profile,
84
-
FollowStatus: followStatus,
85
-
FollowersCount: followStats.Followers,
86
-
FollowingCount: followStats.Following,
87
-
},
88
}
89
}
90
91
-
func (s *State) profileHomePage(w http.ResponseWriter, r *http.Request) {
92
-
pageWithProfile := s.profilePage(w, r)
93
-
if pageWithProfile == nil {
94
return
95
}
96
97
-
id := pageWithProfile.Id
98
repos, err := db.GetRepos(
99
s.db,
100
0,
101
-
db.FilterEq("did", id.DID),
102
)
103
if err != nil {
104
-
log.Printf("getting repos for %s: %s", id.DID, err)
105
}
106
107
-
profile := pageWithProfile.Card.Profile
108
// filter out ones that are pinned
109
pinnedRepos := []db.Repo{}
110
for i, r := range repos {
111
// if this is a pinned repo, add it
112
-
if slices.Contains(profile.PinnedRepos[:], r.RepoAt()) {
113
pinnedRepos = append(pinnedRepos, r)
114
}
115
116
// if there are no saved pins, add the first 4 repos
117
-
if profile.IsPinnedReposEmpty() && i < 4 {
118
pinnedRepos = append(pinnedRepos, r)
119
}
120
}
121
122
-
collaboratingRepos, err := db.CollaboratingIn(s.db, id.DID.String())
123
if err != nil {
124
-
log.Printf("getting collaborating repos for %s: %s", id.DID, err)
125
}
126
127
pinnedCollaboratingRepos := []db.Repo{}
128
for _, r := range collaboratingRepos {
129
// if this is a pinned repo, add it
130
-
if slices.Contains(profile.PinnedRepos[:], r.RepoAt()) {
131
pinnedCollaboratingRepos = append(pinnedCollaboratingRepos, r)
132
}
133
}
134
135
-
timeline, err := db.MakeProfileTimeline(s.db, id.DID.String())
136
if err != nil {
137
-
log.Printf("failed to create profile timeline for %s: %s", id.DID, err)
138
}
139
140
-
var didsToResolve []string
141
-
for _, r := range collaboratingRepos {
142
-
didsToResolve = append(didsToResolve, r.Did)
143
-
}
144
-
for _, byMonth := range timeline.ByMonth {
145
-
for _, pe := range byMonth.PullEvents.Items {
146
-
didsToResolve = append(didsToResolve, pe.Repo.Did)
147
-
}
148
-
for _, ie := range byMonth.IssueEvents.Items {
149
-
didsToResolve = append(didsToResolve, ie.Metadata.Repo.Did)
150
-
}
151
-
for _, re := range byMonth.RepoEvents {
152
-
didsToResolve = append(didsToResolve, re.Repo.Did)
153
-
if re.Source != nil {
154
-
didsToResolve = append(didsToResolve, re.Source.Did)
155
-
}
156
-
}
157
}
158
159
-
now := time.Now()
160
-
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
161
-
punchcard, err := db.MakePunchcard(
162
s.db,
163
-
db.FilterEq("did", id.DID),
164
-
db.FilterGte("date", startOfYear.Format(time.DateOnly)),
165
-
db.FilterLte("date", now.Format(time.DateOnly)),
166
)
167
if err != nil {
168
-
log.Println("failed to get punchcard for did", "did", id.DID, "err", err)
169
}
170
171
-
s.pages.ProfileHomePage(w, pages.ProfileHomePageParams{
172
-
LoggedInUser: pageWithProfile.LoggedInUser,
173
-
Repos: pinnedRepos,
174
-
CollaboratingRepos: pinnedCollaboratingRepos,
175
-
Card: pageWithProfile.Card,
176
-
Punchcard: punchcard,
177
-
ProfileTimeline: timeline,
178
})
179
}
180
181
-
func (s *State) reposPage(w http.ResponseWriter, r *http.Request) {
182
-
pageWithProfile := s.profilePage(w, r)
183
-
if pageWithProfile == nil {
184
return
185
}
186
187
-
id := pageWithProfile.Id
188
repos, err := db.GetRepos(
189
s.db,
190
0,
191
-
db.FilterEq("did", id.DID),
192
)
193
if err != nil {
194
-
log.Printf("getting repos for %s: %s", id.DID, err)
195
}
196
197
-
s.pages.ReposPage(w, pages.ReposPageParams{
198
-
LoggedInUser: pageWithProfile.LoggedInUser,
199
Repos: repos,
200
-
Card: pageWithProfile.Card,
201
})
202
}
203
204
type FollowsPageParams struct {
205
-
LoggedInUser *oauth.User
206
-
Follows []pages.FollowCard
207
-
Card pages.ProfileCard
208
}
209
210
-
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) {
211
-
pageWithProfile := s.profilePage(w, r)
212
-
if pageWithProfile == nil {
213
-
return FollowsPageParams{}, nil
214
}
215
216
-
id := pageWithProfile.Id
217
-
loggedInUser := pageWithProfile.LoggedInUser
218
219
-
follows, err := fetchFollows(s.db, id.DID.String())
220
if err != nil {
221
-
log.Printf("getting followers for %s: %s", id.DID, err)
222
-
return FollowsPageParams{}, err
223
}
224
225
if len(follows) == 0 {
226
-
return FollowsPageParams{
227
-
LoggedInUser: loggedInUser,
228
-
Follows: []pages.FollowCard{},
229
-
Card: pageWithProfile.Card,
230
-
}, nil
231
}
232
233
followDids := make([]string, 0, len(follows))
···
237
238
profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids))
239
if err != nil {
240
-
log.Printf("getting profile for %s: %s", followDids, err)
241
-
return FollowsPageParams{}, err
242
}
243
244
followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids)
···
246
log.Printf("getting follow counts for %s: %s", followDids, err)
247
}
248
249
-
var loggedInUserFollowing map[string]struct{}
250
if loggedInUser != nil {
251
following, err := db.GetFollowing(s.db, loggedInUser.Did)
252
if err != nil {
253
-
return FollowsPageParams{}, err
254
}
255
-
if len(following) > 0 {
256
-
loggedInUserFollowing = make(map[string]struct{}, len(following))
257
-
for _, follow := range following {
258
-
loggedInUserFollowing[follow.SubjectDid] = struct{}{}
259
-
}
260
}
261
}
262
263
-
followCards := make([]pages.FollowCard, 0, len(follows))
264
-
for _, did := range followDids {
265
-
followStats, exists := followStatsMap[did]
266
-
if !exists {
267
-
followStats = db.FollowStats{}
268
-
}
269
followStatus := db.IsNotFollowing
270
-
if loggedInUserFollowing != nil {
271
-
if _, exists := loggedInUserFollowing[did]; exists {
272
-
followStatus = db.IsFollowing
273
-
} else if loggedInUser.Did == did {
274
-
followStatus = db.IsSelf
275
-
}
276
}
277
var profile *db.Profile
278
if p, exists := profiles[did]; exists {
279
profile = p
···
281
profile = &db.Profile{}
282
profile.Did = did
283
}
284
-
followCards = append(followCards, pages.FollowCard{
285
UserDid: did,
286
FollowStatus: followStatus,
287
FollowersCount: followStats.Followers,
288
FollowingCount: followStats.Following,
289
Profile: profile,
290
-
})
291
}
292
293
-
return FollowsPageParams{
294
-
LoggedInUser: loggedInUser,
295
-
Follows: followCards,
296
-
Card: pageWithProfile.Card,
297
}, nil
298
}
299
300
func (s *State) followersPage(w http.ResponseWriter, r *http.Request) {
301
-
followPage, err := s.followPage(w, r, db.GetFollowers, func(f db.Follow) string { return f.UserDid })
302
if err != nil {
303
s.pages.Notice(w, "all-followers", "Failed to load followers")
304
return
305
}
306
307
-
s.pages.FollowersPage(w, pages.FollowersPageParams{
308
-
LoggedInUser: followPage.LoggedInUser,
309
Followers: followPage.Follows,
310
Card: followPage.Card,
311
})
312
}
313
314
func (s *State) followingPage(w http.ResponseWriter, r *http.Request) {
315
-
followPage, err := s.followPage(w, r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid })
316
if err != nil {
317
s.pages.Notice(w, "all-following", "Failed to load following")
318
return
319
}
320
321
-
s.pages.FollowingPage(w, pages.FollowingPageParams{
322
-
LoggedInUser: followPage.LoggedInUser,
323
Following: followPage.Follows,
324
Card: followPage.Card,
325
})
···
17
"github.com/gorilla/feeds"
18
"tangled.sh/tangled.sh/core/api/tangled"
19
"tangled.sh/tangled.sh/core/appview/db"
20
+
// "tangled.sh/tangled.sh/core/appview/oauth"
21
"tangled.sh/tangled.sh/core/appview/pages"
22
)
23
24
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
25
tabVal := r.URL.Query().Get("tab")
26
switch tabVal {
27
+
case "", "overview":
28
+
s.profileOverview(w, r)
29
case "repos":
30
s.reposPage(w, r)
31
case "followers":
32
s.followersPage(w, r)
33
case "following":
34
s.followingPage(w, r)
35
+
case "starred":
36
+
s.starredPage(w, r)
37
}
38
}
39
40
+
func (s *State) profile(r *http.Request) (*pages.ProfileCard, error) {
41
didOrHandle := chi.URLParam(r, "user")
42
if didOrHandle == "" {
43
+
return nil, fmt.Errorf("empty DID or handle")
44
}
45
46
ident, ok := r.Context().Value("resolvedId").(identity.Identity)
47
if !ok {
48
+
return nil, fmt.Errorf("failed to resolve ID")
49
}
50
did := ident.DID.String()
51
52
profile, err := db.GetProfile(s.db, did)
53
if err != nil {
54
+
return nil, fmt.Errorf("failed to get profile: %w", err)
55
}
56
57
followStats, err := db.GetFollowerFollowingCount(s.db, did)
58
if err != nil {
59
+
return nil, fmt.Errorf("failed to get follower stats: %w", err)
60
}
61
62
loggedInUser := s.oauth.GetUser(r)
···
65
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did)
66
}
67
68
+
now := time.Now()
69
+
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
70
+
punchcard, err := db.MakePunchcard(
71
+
s.db,
72
+
db.FilterEq("did", did),
73
+
db.FilterGte("date", startOfYear.Format(time.DateOnly)),
74
+
db.FilterLte("date", now.Format(time.DateOnly)),
75
+
)
76
+
if err != nil {
77
+
return nil, fmt.Errorf("failed to get punchcard for %s: %w", did, err)
78
}
79
+
80
+
return &pages.ProfileCard{
81
+
UserDid: did,
82
+
UserHandle: ident.Handle.String(),
83
+
Profile: profile,
84
+
FollowStatus: followStatus,
85
+
FollowersCount: followStats.Followers,
86
+
FollowingCount: followStats.Following,
87
+
Punchcard: punchcard,
88
+
}, nil
89
}
90
91
+
func (s *State) profileOverview(w http.ResponseWriter, r *http.Request) {
92
+
l := s.logger.With("handler", "profileHomePage")
93
+
94
+
profile, err := s.profile(r)
95
+
if err != nil {
96
+
l.Error("failed to build profile card", "err", err)
97
+
s.pages.Error500(w)
98
return
99
}
100
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
101
102
repos, err := db.GetRepos(
103
s.db,
104
0,
105
+
db.FilterEq("did", profile.UserDid),
106
)
107
if err != nil {
108
+
l.Error("failed to fetch repos", "err", err)
109
}
110
111
// filter out ones that are pinned
112
pinnedRepos := []db.Repo{}
113
for i, r := range repos {
114
// if this is a pinned repo, add it
115
+
if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) {
116
pinnedRepos = append(pinnedRepos, r)
117
}
118
119
// if there are no saved pins, add the first 4 repos
120
+
if profile.Profile.IsPinnedReposEmpty() && i < 4 {
121
pinnedRepos = append(pinnedRepos, r)
122
}
123
}
124
125
+
collaboratingRepos, err := db.CollaboratingIn(s.db, profile.UserDid)
126
if err != nil {
127
+
l.Error("failed to fetch collaborating repos", "err", err)
128
}
129
130
pinnedCollaboratingRepos := []db.Repo{}
131
for _, r := range collaboratingRepos {
132
// if this is a pinned repo, add it
133
+
if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) {
134
pinnedCollaboratingRepos = append(pinnedCollaboratingRepos, r)
135
}
136
}
137
138
+
timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid)
139
if err != nil {
140
+
l.Error("failed to create timeline", "err", err)
141
}
142
143
+
s.pages.ProfileOverview(w, pages.ProfileOverviewParams{
144
+
LoggedInUser: s.oauth.GetUser(r),
145
+
Card: profile,
146
+
Repos: pinnedRepos,
147
+
CollaboratingRepos: pinnedCollaboratingRepos,
148
+
ProfileTimeline: timeline,
149
+
})
150
+
}
151
+
152
+
func (s *State) reposPage(w http.ResponseWriter, r *http.Request) {
153
+
l := s.logger.With("handler", "reposPage")
154
+
155
+
profile, err := s.profile(r)
156
+
if err != nil {
157
+
l.Error("failed to build profile card", "err", err)
158
+
s.pages.Error500(w)
159
+
return
160
}
161
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
162
163
+
repos, err := db.GetRepos(
164
s.db,
165
+
0,
166
+
db.FilterEq("did", profile.UserDid),
167
)
168
if err != nil {
169
+
l.Error("failed to get repos", "err", err)
170
+
s.pages.Error500(w)
171
+
return
172
}
173
174
+
err = s.pages.ProfileRepos(w, pages.ProfileReposParams{
175
+
LoggedInUser: s.oauth.GetUser(r),
176
+
Repos: repos,
177
+
Card: profile,
178
})
179
}
180
181
+
func (s *State) starredPage(w http.ResponseWriter, r *http.Request) {
182
+
l := s.logger.With("handler", "starredPage")
183
+
184
+
profile, err := s.profile(r)
185
+
if err != nil {
186
+
l.Error("failed to build profile card", "err", err)
187
+
s.pages.Error500(w)
188
return
189
}
190
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
191
+
192
+
stars, err := db.GetStars(s.db, 0, db.FilterEq("starred_by_did", profile.UserDid))
193
+
if err != nil {
194
+
l.Error("failed to get stars", "err", err)
195
+
s.pages.Error500(w)
196
+
return
197
+
}
198
+
var repoAts []string
199
+
for _, s := range stars {
200
+
repoAts = append(repoAts, string(s.RepoAt))
201
+
}
202
203
repos, err := db.GetRepos(
204
s.db,
205
0,
206
+
db.FilterIn("at_uri", repoAts),
207
)
208
if err != nil {
209
+
l.Error("failed to get repos", "err", err)
210
+
s.pages.Error500(w)
211
+
return
212
}
213
214
+
err = s.pages.ProfileStarred(w, pages.ProfileStarredParams{
215
+
LoggedInUser: s.oauth.GetUser(r),
216
Repos: repos,
217
+
Card: profile,
218
})
219
}
220
221
type FollowsPageParams struct {
222
+
Follows []pages.FollowCard
223
+
Card *pages.ProfileCard
224
}
225
226
+
func (s *State) followPage(
227
+
r *http.Request,
228
+
fetchFollows func(db.Execer, string) ([]db.Follow, error),
229
+
extractDid func(db.Follow) string,
230
+
) (*FollowsPageParams, error) {
231
+
l := s.logger.With("handler", "reposPage")
232
+
233
+
profile, err := s.profile(r)
234
+
if err != nil {
235
+
return nil, err
236
}
237
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
238
239
+
loggedInUser := s.oauth.GetUser(r)
240
241
+
follows, err := fetchFollows(s.db, profile.UserDid)
242
if err != nil {
243
+
l.Error("failed to fetch follows", "err", err)
244
+
return nil, err
245
}
246
247
if len(follows) == 0 {
248
+
return nil, nil
249
}
250
251
followDids := make([]string, 0, len(follows))
···
255
256
profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids))
257
if err != nil {
258
+
l.Error("failed to get profiles", "followDids", followDids, "err", err)
259
+
return nil, err
260
}
261
262
followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids)
···
264
log.Printf("getting follow counts for %s: %s", followDids, err)
265
}
266
267
+
loggedInUserFollowing := make(map[string]struct{})
268
if loggedInUser != nil {
269
following, err := db.GetFollowing(s.db, loggedInUser.Did)
270
if err != nil {
271
+
l.Error("failed to get follow list", "err", err, "loggedInUser", loggedInUser.Did)
272
+
return nil, err
273
}
274
+
loggedInUserFollowing = make(map[string]struct{}, len(following))
275
+
for _, follow := range following {
276
+
loggedInUserFollowing[follow.SubjectDid] = struct{}{}
277
}
278
}
279
280
+
followCards := make([]pages.FollowCard, len(follows))
281
+
for i, did := range followDids {
282
+
followStats := followStatsMap[did]
283
followStatus := db.IsNotFollowing
284
+
if _, exists := loggedInUserFollowing[did]; exists {
285
+
followStatus = db.IsFollowing
286
+
} else if loggedInUser.Did == did {
287
+
followStatus = db.IsSelf
288
}
289
+
290
var profile *db.Profile
291
if p, exists := profiles[did]; exists {
292
profile = p
···
294
profile = &db.Profile{}
295
profile.Did = did
296
}
297
+
followCards[i] = pages.FollowCard{
298
UserDid: did,
299
FollowStatus: followStatus,
300
FollowersCount: followStats.Followers,
301
FollowingCount: followStats.Following,
302
Profile: profile,
303
+
}
304
}
305
306
+
return &FollowsPageParams{
307
+
Follows: followCards,
308
+
Card: profile,
309
}, nil
310
}
311
312
func (s *State) followersPage(w http.ResponseWriter, r *http.Request) {
313
+
followPage, err := s.followPage(r, db.GetFollowers, func(f db.Follow) string { return f.UserDid })
314
if err != nil {
315
s.pages.Notice(w, "all-followers", "Failed to load followers")
316
return
317
}
318
319
+
s.pages.ProfileFollowers(w, pages.ProfileFollowersParams{
320
+
LoggedInUser: s.oauth.GetUser(r),
321
Followers: followPage.Follows,
322
Card: followPage.Card,
323
})
324
}
325
326
func (s *State) followingPage(w http.ResponseWriter, r *http.Request) {
327
+
followPage, err := s.followPage(r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid })
328
if err != nil {
329
s.pages.Notice(w, "all-following", "Failed to load following")
330
return
331
}
332
333
+
s.pages.ProfileFollowing(w, pages.ProfileFollowingParams{
334
+
LoggedInUser: s.oauth.GetUser(r),
335
Following: followPage.Follows,
336
Card: followPage.Card,
337
})