Signed-off-by: brookjeynes me@brookjeynes.dev
+56
-40
internal/server/handlers/profile.go
+56
-40
internal/server/handlers/profile.go
···
3
import (
4
"errors"
5
"fmt"
6
-
"log"
7
"net/http"
8
"strconv"
9
"time"
···
30
PendingProfileUpdate string = "pending_profile_update"
31
)
32
33
-
func parseProfileForm(r *http.Request) (db.Profile, error) {
34
err := r.ParseForm()
35
if err != nil {
36
return db.Profile{}, fmt.Errorf("invalid profile form: %w", err)
···
45
for _, code := range languageCodes {
46
language, ok := db.Languages[db.LanguageCode(code)]
47
if !ok {
48
-
log.Printf("invalid language code submitted: %s", code)
49
continue
50
}
51
languages = append(languages, language)
···
62
}
63
64
func (h *Handler) HandleProfilePage(w http.ResponseWriter, r *http.Request) {
65
didOrHandle := chi.URLParam(r, "user")
66
if didOrHandle == "" {
67
http.Error(w, "Bad request", http.StatusBadRequest)
···
102
103
totalStudyTime, err = db.GetTotalStudyTime(h.Db, profileDid)
104
if err != nil {
105
-
log.Println("failed to get total study time:", err)
106
}
107
108
totalStudySessions, _ = db.GetTotalStudySessions(h.Db, profileDid)
109
if err != nil {
110
-
log.Println("failed to get total study study sessions:", err)
111
}
112
113
followers, following, _ = db.GetFollowerFollowingCount(h.Db, profileDid)
114
if err != nil {
115
-
log.Println("failed to get follow stats:", err)
116
}
117
118
streak, _ = db.GetCurrentStreak(h.Db, profileDid)
119
if err != nil {
120
-
log.Println("failed to get streak:", err)
121
}
122
123
if user != nil {
···
128
})
129
130
if err := g.Wait(); err != nil {
131
-
log.Printf("failed to fetch critical profile data for %s: %v", profileDid, err)
132
htmx.HxError(w, http.StatusInternalServerError, "Failed to fetch profile data, try again later.")
133
return
134
}
···
160
161
err := h.Posthog.Enqueue(capture)
162
if err != nil {
163
-
log.Println("failed to enqueue posthog event:", err)
164
}
165
}
166
···
178
}
179
180
func (h *Handler) HandleEditProfilePage(w http.ResponseWriter, r *http.Request) {
181
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
182
if err != nil {
183
-
log.Println("failed to get logged-in user:", err)
184
htmx.HxRedirect(w, "/login")
185
return
186
}
187
188
profile, err := h.GetUserProfileWithAvatar(user.Did)
189
if err != nil {
190
-
log.Printf("failed to find %s in db: %s", user.Did, err)
191
w.WriteHeader(http.StatusNotFound)
192
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
193
return
194
}
195
196
if user.Did != profile.Did {
197
-
log.Printf("user '%s' does not own record '%s'", user.Did, profile.Did)
198
htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this profile.")
199
return
200
}
···
214
case http.MethodPost:
215
client, err := h.Oauth.AuthorizedClient(r)
216
if err != nil {
217
-
log.Println("failed to get authorized client:", err)
218
htmx.HxRedirect(w, "/login")
219
return
220
}
221
222
-
updatedProfile, err := parseProfileForm(r)
223
if err != nil {
224
-
log.Println("invalid profile form:", err)
225
htmx.HxError(w, http.StatusBadRequest, "Failed to update profile, ensure all fields contain valid data.")
226
return
227
}
···
236
}
237
238
if err := db.ValidateProfile(updatedProfile); err != nil {
239
-
log.Println("invalid profile:", err)
240
switch {
241
case errors.Is(err, db.ErrProfileNameTooLong):
242
htmx.HxError(w, http.StatusBadRequest, "Profile name cannot be more than 64 characters.")
···
280
SwapRecord: cid,
281
})
282
if err != nil {
283
-
log.Println("failed to put profile record:", err)
284
htmx.HxError(w, http.StatusInternalServerError, "Failed to update PDS, try again later.")
285
return
286
}
287
288
err = SavePendingUpdate(h, w, r, PendingProfileUpdate, profile)
289
if err != nil {
290
-
log.Printf("failed to save yoten-session to add pending profile update: %v", err)
291
}
292
293
if !h.Config.Core.Dev {
···
308
Properties: properties,
309
})
310
if err != nil {
311
-
log.Println("failed to enqueue posthog identify event:", err)
312
}
313
314
err = h.Posthog.Enqueue(posthog.Capture{
···
316
Event: ph.ProfileRecordEditedEvent,
317
})
318
if err != nil {
319
-
log.Println("failed to enqueue posthog event:", err)
320
}
321
}
322
···
325
}
326
327
func (h *Handler) HandleResourcesPage(w http.ResponseWriter, r *http.Request) {
328
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
329
if err != nil {
330
-
log.Println("failed to get logged-in user:", err)
331
htmx.HxRedirect(w, "/login")
332
return
333
}
334
335
resources, err := db.GetResourcesByDid(h.Db, user.Did)
336
if err != nil {
337
-
log.Println("failed to get resources:", err)
338
htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve profile resources, try again later.")
339
return
340
}
341
342
resources, err = ApplyPendingChanges(h, w, r, resources, PendingResourceCreation, PendingResourceUpdates, PendingResourceDeletion)
343
if err != nil {
344
-
log.Printf("failed to save yoten-session after processing pending changes: %v", err)
345
}
346
347
activeResources := utils.Filter(resources, func(resource db.Resource) bool {
···
355
}
356
357
func (h *Handler) HandleActivitiesPage(w http.ResponseWriter, r *http.Request) {
358
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
359
if err != nil {
360
-
log.Println("failed to get logged-in user:", err)
361
htmx.HxRedirect(w, "/login")
362
return
363
}
364
365
activities, err := db.GetActivitiesByDid(h.Db, user.Did)
366
if err != nil {
367
-
log.Println("failed to get activities:", err)
368
htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve profile activities, try again later.")
369
return
370
}
371
372
activities, err = ApplyPendingChanges(h, w, r, activities, PendingActivityCreation, PendingActivityUpdates, PendingActivityDeletion)
373
if err != nil {
374
-
log.Printf("failed to save yoten-session after processing pending changes: %v", err)
375
}
376
377
activeActivities := utils.Filter(activities, func(activity db.Activity) bool {
···
400
}
401
402
func (h *Handler) HandleProfileFeed(w http.ResponseWriter, r *http.Request) {
403
didOrHandle := chi.URLParam(r, "user")
404
if didOrHandle == "" {
405
http.Error(w, "Bad request", http.StatusBadRequest)
···
415
416
profile, err := h.GetUserProfileWithAvatar(ident.DID.String())
417
if err != nil {
418
-
log.Printf("failed to find %s in db: %s", ident.DID.String(), err)
419
w.WriteHeader(http.StatusNotFound)
420
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
421
return
···
423
424
bskyProfile, err := bsky.GetBskyProfile(ident.DID.String())
425
if err != nil {
426
-
log.Println("failed to get bsky profile:", err)
427
w.WriteHeader(http.StatusNotFound)
428
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
429
return
···
435
}
436
page, err := strconv.ParseInt(pageStr, 10, 64)
437
if err != nil {
438
-
log.Println("failed to parse page value:", err)
439
page = 1
440
}
441
if page == 0 {
···
447
448
sessions, err := db.GetStudySessionLogs(h.Db, ident.DID.String(), pageSize+1, int(offset))
449
if err != nil {
450
-
log.Println("failed to get study sessions:", err)
451
w.WriteHeader(http.StatusNotFound)
452
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
453
return
···
455
456
sessions, err = ApplyPendingChanges(h, w, r, sessions, PendingStudySessionCreation, PendingStudySessionUpdates, PendingStudySessionDeletion)
457
if err != nil {
458
-
log.Printf("failed to save yoten-session after processing pending changes: %v", err)
459
}
460
461
feed := []*db.StudySessionFeedItem{}
···
493
}
494
495
func (h *Handler) HandleFriendsPage(w http.ResponseWriter, r *http.Request) {
496
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
497
if err != nil {
498
-
log.Println("failed to get logged-in user:", err)
499
htmx.HxRedirect(w, "/login")
500
return
501
}
502
503
followers, following, err := db.GetFollowerFollowingCount(h.Db, user.Did)
504
if err != nil {
505
-
log.Printf("getting follow stats repos for %s: %s", user.Did, err)
506
}
507
508
views.FriendsPage(views.FriendsPageParams{
···
513
}
514
515
func (h *Handler) HandleFriendsFeed(w http.ResponseWriter, r *http.Request) {
516
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
517
if err != nil {
518
-
log.Println("failed to get logged-in user")
519
htmx.HxRedirect(w, "/login")
520
return
521
}
···
529
}
530
page, err := strconv.ParseInt(pageStr, 10, 64)
531
if err != nil {
532
-
log.Println("failed to parse page value:", err)
533
page = 1
534
}
535
if page == 0 {
···
545
if mode == partials.Following {
546
feed, err := db.GetFollowing(h.Db, user.Did, pageSize+1, int(offset))
547
if err != nil {
548
-
log.Println("failed to get following list:", err)
549
htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.")
550
return
551
}
552
bskyHydratedFeed, err = h.GetHydratedFollowerProfiles(feed)
553
if err != nil {
554
-
log.Println("failed to hydrate bsky profiles:", err)
555
htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.")
556
return
557
}
558
} else {
559
feed, err := db.GetFollowers(h.Db, user.Did, pageSize+1, int(offset))
560
if err != nil {
561
-
log.Println("failed to get followers list:", err)
562
htmx.HxError(w, http.StatusInternalServerError, "Failed to get followers list, try again later.")
563
return
564
}
565
bskyHydratedFeed, err = h.GetHydratedFollowerProfiles(feed)
566
if err != nil {
567
-
log.Println("failed to hydrate bsky profiles:", err)
568
htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.")
569
return
570
}
···
586
}
587
588
func (h *Handler) HandleNotificationsPage(w http.ResponseWriter, r *http.Request) {
589
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
590
if err != nil {
591
-
log.Println("failed to get logged-in user:", err)
592
htmx.HxRedirect(w, "/login")
593
return
594
}
···
3
import (
4
"errors"
5
"fmt"
6
+
"log/slog"
7
"net/http"
8
"strconv"
9
"time"
···
30
PendingProfileUpdate string = "pending_profile_update"
31
)
32
33
+
func parseProfileForm(r *http.Request, logger *slog.Logger) (db.Profile, error) {
34
err := r.ParseForm()
35
if err != nil {
36
return db.Profile{}, fmt.Errorf("invalid profile form: %w", err)
···
45
for _, code := range languageCodes {
46
language, ok := db.Languages[db.LanguageCode(code)]
47
if !ok {
48
+
logger.Warn("invalid language code submitted", "languageCode", code)
49
continue
50
}
51
languages = append(languages, language)
···
62
}
63
64
func (h *Handler) HandleProfilePage(w http.ResponseWriter, r *http.Request) {
65
+
l := h.Logger.With("handler", "HandleProfilePage")
66
+
67
didOrHandle := chi.URLParam(r, "user")
68
if didOrHandle == "" {
69
http.Error(w, "Bad request", http.StatusBadRequest)
···
104
105
totalStudyTime, err = db.GetTotalStudyTime(h.Db, profileDid)
106
if err != nil {
107
+
l.Error("failed to get total study time", "err", err)
108
}
109
110
totalStudySessions, _ = db.GetTotalStudySessions(h.Db, profileDid)
111
if err != nil {
112
+
l.Error("failed to get total study study sessions", "err", err)
113
}
114
115
followers, following, _ = db.GetFollowerFollowingCount(h.Db, profileDid)
116
if err != nil {
117
+
l.Error("failed to get follow stats", "err", err)
118
}
119
120
streak, _ = db.GetCurrentStreak(h.Db, profileDid)
121
if err != nil {
122
+
l.Error("failed to get streak", "err", err)
123
}
124
125
if user != nil {
···
130
})
131
132
if err := g.Wait(); err != nil {
133
+
l.Error("failed to fetch critical profile data for", "did", profileDid, "err", err)
134
htmx.HxError(w, http.StatusInternalServerError, "Failed to fetch profile data, try again later.")
135
return
136
}
···
162
163
err := h.Posthog.Enqueue(capture)
164
if err != nil {
165
+
l.Error("failed to enqueue posthog event", "err", err)
166
}
167
}
168
···
180
}
181
182
func (h *Handler) HandleEditProfilePage(w http.ResponseWriter, r *http.Request) {
183
+
l := h.Logger.With("handler", "HandleEditProfilePage")
184
+
185
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
186
if err != nil {
187
+
l.Error("failed to get logged-in user", "err", err)
188
htmx.HxRedirect(w, "/login")
189
return
190
}
191
192
profile, err := h.GetUserProfileWithAvatar(user.Did)
193
if err != nil {
194
+
l.Error("failed to find user in db", "did", user.Did, "err", err)
195
w.WriteHeader(http.StatusNotFound)
196
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
197
return
198
}
199
200
if user.Did != profile.Did {
201
+
l.Error("user does not own record", "did", user.Did, "profileDid", profile.Did)
202
htmx.HxError(w, http.StatusUnauthorized, "You do not have permissions to edit this profile.")
203
return
204
}
···
218
case http.MethodPost:
219
client, err := h.Oauth.AuthorizedClient(r)
220
if err != nil {
221
+
l.Error("failed to get authorized client", "err", err)
222
htmx.HxRedirect(w, "/login")
223
return
224
}
225
226
+
updatedProfile, err := parseProfileForm(r, l)
227
if err != nil {
228
+
l.Error("invalid profile form", "err", err)
229
htmx.HxError(w, http.StatusBadRequest, "Failed to update profile, ensure all fields contain valid data.")
230
return
231
}
···
240
}
241
242
if err := db.ValidateProfile(updatedProfile); err != nil {
243
+
l.Error("invalid profile", "err", err)
244
switch {
245
case errors.Is(err, db.ErrProfileNameTooLong):
246
htmx.HxError(w, http.StatusBadRequest, "Profile name cannot be more than 64 characters.")
···
284
SwapRecord: cid,
285
})
286
if err != nil {
287
+
l.Error("failed to put profile record", "err", err)
288
htmx.HxError(w, http.StatusInternalServerError, "Failed to update PDS, try again later.")
289
return
290
}
291
292
err = SavePendingUpdate(h, w, r, PendingProfileUpdate, profile)
293
if err != nil {
294
+
l.Error("failed to save yoten-session to add pending profile update", "err", err)
295
}
296
297
if !h.Config.Core.Dev {
···
312
Properties: properties,
313
})
314
if err != nil {
315
+
l.Error("failed to enqueue posthog identify event", "err", err)
316
}
317
318
err = h.Posthog.Enqueue(posthog.Capture{
···
320
Event: ph.ProfileRecordEditedEvent,
321
})
322
if err != nil {
323
+
l.Error("failed to enqueue posthog event", "err", err)
324
}
325
}
326
···
329
}
330
331
func (h *Handler) HandleResourcesPage(w http.ResponseWriter, r *http.Request) {
332
+
l := h.Logger.With("handler", "HandleResourcesPage")
333
+
334
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
335
if err != nil {
336
+
l.Error("failed to get logged-in user", "err", err)
337
htmx.HxRedirect(w, "/login")
338
return
339
}
340
341
resources, err := db.GetResourcesByDid(h.Db, user.Did)
342
if err != nil {
343
+
l.Error("failed to get resources", "err", err)
344
htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve profile resources, try again later.")
345
return
346
}
347
348
resources, err = ApplyPendingChanges(h, w, r, resources, PendingResourceCreation, PendingResourceUpdates, PendingResourceDeletion)
349
if err != nil {
350
+
l.Error("failed to save yoten-session after processing pending changes", "err", err)
351
}
352
353
activeResources := utils.Filter(resources, func(resource db.Resource) bool {
···
361
}
362
363
func (h *Handler) HandleActivitiesPage(w http.ResponseWriter, r *http.Request) {
364
+
l := h.Logger.With("handler", "HandleActivitiesPage")
365
+
366
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
367
if err != nil {
368
+
l.Error("failed to get logged-in user", "err", err)
369
htmx.HxRedirect(w, "/login")
370
return
371
}
372
373
activities, err := db.GetActivitiesByDid(h.Db, user.Did)
374
if err != nil {
375
+
l.Error("failed to get activities", "err", err)
376
htmx.HxError(w, http.StatusInternalServerError, "Failed to retrieve profile activities, try again later.")
377
return
378
}
379
380
activities, err = ApplyPendingChanges(h, w, r, activities, PendingActivityCreation, PendingActivityUpdates, PendingActivityDeletion)
381
if err != nil {
382
+
l.Error("failed to save yoten-session after processing pending changes", "err", err)
383
}
384
385
activeActivities := utils.Filter(activities, func(activity db.Activity) bool {
···
408
}
409
410
func (h *Handler) HandleProfileFeed(w http.ResponseWriter, r *http.Request) {
411
+
l := h.Logger.With("handler", "HandleProfileFeed")
412
+
413
didOrHandle := chi.URLParam(r, "user")
414
if didOrHandle == "" {
415
http.Error(w, "Bad request", http.StatusBadRequest)
···
425
426
profile, err := h.GetUserProfileWithAvatar(ident.DID.String())
427
if err != nil {
428
+
l.Error("failed to find user in db", "did", ident.DID.String(), "err", err)
429
w.WriteHeader(http.StatusNotFound)
430
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
431
return
···
433
434
bskyProfile, err := bsky.GetBskyProfile(ident.DID.String())
435
if err != nil {
436
+
l.Error("failed to get bsky profile", "err", err)
437
w.WriteHeader(http.StatusNotFound)
438
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
439
return
···
445
}
446
page, err := strconv.ParseInt(pageStr, 10, 64)
447
if err != nil {
448
+
l.Error("failed to parse page value", "err", err)
449
page = 1
450
}
451
if page == 0 {
···
457
458
sessions, err := db.GetStudySessionLogs(h.Db, ident.DID.String(), pageSize+1, int(offset))
459
if err != nil {
460
+
l.Error("failed to get study sessions", "err", err)
461
w.WriteHeader(http.StatusNotFound)
462
views.NotFoundPage(views.NotFoundPageParams{}).Render(r.Context(), w)
463
return
···
465
466
sessions, err = ApplyPendingChanges(h, w, r, sessions, PendingStudySessionCreation, PendingStudySessionUpdates, PendingStudySessionDeletion)
467
if err != nil {
468
+
l.Error("failed to save yoten-session after processing pending changes", "err", err)
469
}
470
471
feed := []*db.StudySessionFeedItem{}
···
503
}
504
505
func (h *Handler) HandleFriendsPage(w http.ResponseWriter, r *http.Request) {
506
+
l := h.Logger.With("handler", "HandleFriendsPage")
507
+
508
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
509
if err != nil {
510
+
l.Error("failed to get logged-in user", "err", err)
511
htmx.HxRedirect(w, "/login")
512
return
513
}
514
515
followers, following, err := db.GetFollowerFollowingCount(h.Db, user.Did)
516
if err != nil {
517
+
l.Error("failed to get follow stats", "err", err)
518
}
519
520
views.FriendsPage(views.FriendsPageParams{
···
525
}
526
527
func (h *Handler) HandleFriendsFeed(w http.ResponseWriter, r *http.Request) {
528
+
l := h.Logger.With("handler", "HandleFriendsFeed")
529
+
530
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
531
if err != nil {
532
+
l.Error("failed to get logged-in user")
533
htmx.HxRedirect(w, "/login")
534
return
535
}
···
543
}
544
page, err := strconv.ParseInt(pageStr, 10, 64)
545
if err != nil {
546
+
l.Error("failed to parse page value", "err", err)
547
page = 1
548
}
549
if page == 0 {
···
559
if mode == partials.Following {
560
feed, err := db.GetFollowing(h.Db, user.Did, pageSize+1, int(offset))
561
if err != nil {
562
+
l.Error("failed to get following list", "err", err)
563
htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.")
564
return
565
}
566
bskyHydratedFeed, err = h.GetHydratedFollowerProfiles(feed)
567
if err != nil {
568
+
l.Error("failed to hydrate bsky profiles", "err", err)
569
htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.")
570
return
571
}
572
} else {
573
feed, err := db.GetFollowers(h.Db, user.Did, pageSize+1, int(offset))
574
if err != nil {
575
+
l.Error("failed to get followers list", "err", err)
576
htmx.HxError(w, http.StatusInternalServerError, "Failed to get followers list, try again later.")
577
return
578
}
579
bskyHydratedFeed, err = h.GetHydratedFollowerProfiles(feed)
580
if err != nil {
581
+
l.Error("failed to hydrate bsky profiles", "err", err)
582
htmx.HxError(w, http.StatusInternalServerError, "Failed to get following list, try again later.")
583
return
584
}
···
600
}
601
602
func (h *Handler) HandleNotificationsPage(w http.ResponseWriter, r *http.Request) {
603
+
l := h.Logger.With("handler", "HandleNotificationsPage")
604
+
605
user, err := bsky.GetUserWithBskyProfile(h.Oauth, r)
606
if err != nil {
607
+
l.Error("failed to get logged-in user", "err", err)
608
htmx.HxRedirect(w, "/login")
609
return
610
}