This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / state / markup.go
2.8 kB 88 lines
1package state 2 3import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strings" 8 9 comatproto "github.com/bluesky-social/indigo/api/atproto" 10 "tangled.org/core/xrpc" 11) 12 13const maxBlobSize = 1_000_000 14 15func (s *State) MarkdownPreview(w http.ResponseWriter, r *http.Request) { 16 body := r.FormValue("body") 17 s.pages.MarkdownPreviewFragment(w, body) 18} 19 20// MarkupUpload proxies an image upload to the user's PDS via uploadBlob and 21// returns the blob ref plus a blob+at://<did>/<cid> URI. The browser can't call 22// uploadBlob directly (the DPoP key lives server-side), so this is the bridge. 23func (s *State) MarkupUpload(w http.ResponseWriter, r *http.Request) { 24 l := s.logger.With("handler", "MarkupUpload") 25 26 user := s.oauth.GetMultiAccountUser(r) 27 if user == nil { 28 writeUploadError(w, http.StatusUnauthorized, "not logged in") 29 return 30 } 31 l = l.With("did", user.Did) 32 33 contentType := r.Header.Get("Content-Type") 34 if !strings.HasPrefix(contentType, "image/") { 35 writeUploadError(w, http.StatusUnsupportedMediaType, "only image uploads are allowed") 36 return 37 } 38 39 // cap the body at the lexicon's maxSize (MaxBytesReader errors past it) 40 r.Body = http.MaxBytesReader(w, r.Body, maxBlobSize) 41 defer r.Body.Close() 42 43 client, err := s.oauth.AuthorizedClient(r) 44 if err != nil { 45 l.Error("failed to get authorized client", "err", err) 46 writeUploadError(w, http.StatusBadGateway, "failed to connect to your PDS") 47 return 48 } 49 50 // pre-warm DPoP nonce 51 if _, err := comatproto.ServerGetSession(r.Context(), client); err != nil { 52 l.Error("failed to pre-warm session", "err", err) 53 writeUploadError(w, http.StatusInternalServerError, "failed to pre-warm session") 54 return 55 } 56 57 resp, err := xrpc.RepoUploadBlob(r.Context(), client, r.Body, contentType) 58 if err != nil { 59 // MaxBytesReader's over-limit error surfaces through LexDo 60 if strings.Contains(err.Error(), "request body too large") { 61 l.Warn("upload exceeds size limit") 62 writeUploadError(w, http.StatusRequestEntityTooLarge, "image too large (max 1MB)") 63 return 64 } 65 l.Error("failed to upload blob", "err", err) 66 writeUploadError(w, http.StatusBadGateway, "failed to upload image to your PDS") 67 return 68 } 69 70 blob := resp.Blob 71 cid := blob.Ref.String() 72 l.Info("uploaded blob", "cid", cid, "size", blob.Size) 73 74 w.Header().Set("Content-Type", "application/json") 75 if err := json.NewEncoder(w).Encode(map[string]any{ 76 "blob": blob, 77 "did": user.Did, 78 "uri": fmt.Sprintf("blob+at://%s/%s", user.Did, cid), 79 }); err != nil { 80 l.Error("failed to encode upload response", "err", err) 81 } 82} 83 84func writeUploadError(w http.ResponseWriter, status int, msg string) { 85 w.Header().Set("Content-Type", "application/json") 86 w.WriteHeader(status) 87 _ = json.NewEncoder(w).Encode(map[string]string{"error": msg}) 88}