Monorepo for Tangled tangled.org

appview/state: handle profile pic uploads #892

merged opened by anirudh.fi targeting master from icy/tolqpt
Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:hwevmowznbiukdf6uk5dwrrq/sh.tangled.repo.pull/3m7znwnzpve22
+122
Diff #6
+6
appview/ingester.go
··· 285 285 return err 286 286 } 287 287 288 + avatar := "" 289 + if record.Avatar != nil { 290 + avatar = record.Avatar.Ref.String() 291 + } 292 + 288 293 description := "" 289 294 if record.Description != nil { 290 295 description = *record.Description ··· 325 330 326 331 profile := models.Profile{ 327 332 Did: did, 333 + Avatar: avatar, 328 334 Description: description, 329 335 IncludeBluesky: includeBluesky, 330 336 Location: location,
+115
appview/state/profile.go
··· 739 739 AllRepos: allRepos, 740 740 }) 741 741 } 742 + 743 + func (s *State) UploadProfileAvatar(w http.ResponseWriter, r *http.Request) { 744 + l := s.logger.With("handler", "UploadProfileAvatar") 745 + user := s.oauth.GetUser(r) 746 + l = l.With("did", user.Did) 747 + 748 + // Parse multipart form (10MB max) 749 + if err := r.ParseMultipartForm(10 << 20); err != nil { 750 + l.Error("failed to parse form", "err", err) 751 + s.pages.Notice(w, "avatar-error", "Failed to parse form") 752 + return 753 + } 754 + 755 + file, handler, err := r.FormFile("avatar") 756 + if err != nil { 757 + l.Error("failed to read avatar file", "err", err) 758 + s.pages.Notice(w, "avatar-error", "Failed to read avatar file") 759 + return 760 + } 761 + defer file.Close() 762 + 763 + if handler.Size > 1000000 { 764 + l.Warn("avatar file too large", "size", handler.Size) 765 + s.pages.Notice(w, "avatar-error", "Avatar file too large (max 1MB)") 766 + return 767 + } 768 + 769 + contentType := handler.Header.Get("Content-Type") 770 + if contentType != "image/png" && contentType != "image/jpeg" { 771 + l.Warn("invalid image type", "contentType", contentType) 772 + s.pages.Notice(w, "avatar-error", "Invalid image type (only PNG and JPEG allowed)") 773 + return 774 + } 775 + 776 + client, err := s.oauth.AuthorizedClient(r) 777 + if err != nil { 778 + l.Error("failed to get PDS client", "err", err) 779 + s.pages.Notice(w, "avatar-error", "Failed to connect to your PDS") 780 + return 781 + } 782 + 783 + uploadBlobResp, err := comatproto.RepoUploadBlob(r.Context(), client, file) 784 + if err != nil { 785 + l.Error("failed to upload avatar blob", "err", err) 786 + s.pages.Notice(w, "avatar-error", "Failed to upload avatar to your PDS") 787 + return 788 + } 789 + 790 + l.Info("uploaded avatar blob", "cid", uploadBlobResp.Blob.Ref.String()) 791 + 792 + // get current profile record from PDS to get its CID for swap 793 + getRecordResp, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.ActorProfileNSID, user.Did, "self") 794 + if err != nil { 795 + l.Error("failed to get current profile record", "err", err) 796 + s.pages.Notice(w, "avatar-error", "Failed to get current profile from your PDS") 797 + return 798 + } 799 + 800 + var profileRecord *tangled.ActorProfile 801 + if getRecordResp.Value != nil { 802 + if val, ok := getRecordResp.Value.Val.(*tangled.ActorProfile); ok { 803 + profileRecord = val 804 + } else { 805 + l.Warn("profile record type assertion failed, creating new record") 806 + profileRecord = &tangled.ActorProfile{} 807 + } 808 + } else { 809 + l.Warn("no existing profile record, creating new record") 810 + profileRecord = &tangled.ActorProfile{} 811 + } 812 + 813 + profileRecord.Avatar = uploadBlobResp.Blob 814 + 815 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 816 + Collection: tangled.ActorProfileNSID, 817 + Repo: user.Did, 818 + Rkey: "self", 819 + Record: &lexutil.LexiconTypeDecoder{Val: profileRecord}, 820 + SwapRecord: getRecordResp.Cid, 821 + }) 822 + 823 + if err != nil { 824 + l.Error("failed to update profile record", "err", err) 825 + s.pages.Notice(w, "avatar-error", "Failed to update profile on your PDS") 826 + return 827 + } 828 + 829 + l.Info("successfully updated profile with avatar") 830 + 831 + profile, err := db.GetProfile(s.db, user.Did) 832 + if err != nil { 833 + l.Warn("getting profile data from DB", "err", err) 834 + profile = &models.Profile{Did: user.Did} 835 + } 836 + profile.Avatar = uploadBlobResp.Blob.Ref.String() 837 + 838 + tx, err := s.db.BeginTx(r.Context(), nil) 839 + if err != nil { 840 + l.Error("failed to start transaction", "err", err) 841 + s.pages.HxRefresh(w) 842 + w.WriteHeader(http.StatusOK) 843 + return 844 + } 845 + 846 + err = db.UpsertProfile(tx, profile) 847 + if err != nil { 848 + l.Error("failed to update profile in DB", "err", err) 849 + s.pages.HxRefresh(w) 850 + w.WriteHeader(http.StatusOK) 851 + return 852 + } 853 + 854 + w.Header().Set("HX-Redirect", r.Header.Get("Referer")) 855 + w.WriteHeader(http.StatusOK) 856 + }
+1
appview/state/router.go
··· 165 165 r.Get("/edit-pins", s.EditPinsFragment) 166 166 r.Post("/bio", s.UpdateProfileBio) 167 167 r.Post("/pins", s.UpdateProfilePins) 168 + r.Post("/avatar", s.UploadProfileAvatar) 168 169 }) 169 170 170 171 r.Mount("/settings", s.SettingsRouter())

History

7 rounds 1 comment
sign up or login to add to the discussion
1 commit
expand
appview/state: handle profile pic uploads
3/3 success
expand
expand 0 comments
pull request successfully merged
1 commit
expand
appview/state: handle profile pic uploads
3/3 success
expand
expand 0 comments
1 commit
expand
appview/state: handle profile pic uploads
3/3 success
expand
expand 0 comments
1 commit
expand
appview/state: handle profile pic uploads
3/3 success
expand
expand 0 comments
1 commit
expand
appview/state: handle profile pic uploads
expand 0 comments
1 commit
expand
appview/state: handle profile pic uploads
expand 1 comment

we need similar logic to accept the blob CID from firehose ingester here. the existing ingester will include a null CID because it uses this constructor:

		profile := models.Profile{
			Did:            did,
			Description:    description,
			IncludeBluesky: includeBluesky,
			Location:       location,
			Links:          links,
			Stats:          stats,
			PinnedRepos:    pinned,
			Pronouns:       pronouns,
		}
1 commit
expand
appview/state: handle profile pic uploads
expand 0 comments