Signed-off-by: Anirudh Oppiliappan anirudh@tangled.org
+6
appview/ingester.go
+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
+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
+
}
History
7 rounds
1 comment
anirudh.fi
submitted
#6
1 commit
expand
collapse
appview/state: handle profile pic uploads
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
pull request successfully merged
anirudh.fi
submitted
#5
1 commit
expand
collapse
appview/state: handle profile pic uploads
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#4
1 commit
expand
collapse
appview/state: handle profile pic uploads
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#3
1 commit
expand
collapse
appview/state: handle profile pic uploads
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#2
1 commit
expand
collapse
appview/state: handle profile pic uploads
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
expand 0 comments
anirudh.fi
submitted
#1
1 commit
expand
collapse
appview/state: handle profile pic uploads
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
expand 1 comment
anirudh.fi
submitted
#0
1 commit
expand
collapse
appview/state: handle profile pic uploads
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
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: