forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at oauth 7.7 kB view raw
1package state 2 3import ( 4 "fmt" 5 "log" 6 "net/http" 7 "net/url" 8 "time" 9 10 comatproto "github.com/bluesky-social/indigo/api/atproto" 11 lexutil "github.com/bluesky-social/indigo/lex/util" 12 "github.com/dustin/go-humanize" 13 "github.com/go-chi/chi/v5" 14 "github.com/go-git/go-git/v5/plumbing" 15 "github.com/ipfs/go-cid" 16 "tangled.sh/tangled.sh/core/api/tangled" 17 "tangled.sh/tangled.sh/core/appview" 18 "tangled.sh/tangled.sh/core/appview/db" 19 "tangled.sh/tangled.sh/core/appview/pages" 20 "tangled.sh/tangled.sh/core/types" 21) 22 23// TODO: proper statuses here on early exit 24func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) { 25 user := s.oauth.GetUser(r) 26 tagParam := chi.URLParam(r, "tag") 27 f, err := s.fullyResolvedRepo(r) 28 if err != nil { 29 log.Println("failed to get repo and knot", err) 30 s.pages.Notice(w, "upload", "failed to upload artifact, error in repo resolution") 31 return 32 } 33 34 tag, err := s.resolveTag(f, tagParam) 35 if err != nil { 36 log.Println("failed to resolve tag", err) 37 s.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution") 38 return 39 } 40 41 file, handler, err := r.FormFile("artifact") 42 if err != nil { 43 log.Println("failed to upload artifact", err) 44 s.pages.Notice(w, "upload", "failed to upload artifact") 45 return 46 } 47 defer file.Close() 48 49 client, err := s.oauth.AuthorizedClient(r) 50 if err != nil { 51 log.Println("failed to get authorized client", err) 52 s.pages.Notice(w, "upload", "failed to get authorized client") 53 return 54 } 55 56 uploadBlobResp, err := client.RepoUploadBlob(r.Context(), file) 57 if err != nil { 58 log.Println("failed to upload blob", err) 59 s.pages.Notice(w, "upload", "Failed to upload blob to your PDS. Try again later.") 60 return 61 } 62 63 log.Println("uploaded blob", humanize.Bytes(uint64(uploadBlobResp.Blob.Size)), uploadBlobResp.Blob.Ref.String()) 64 65 rkey := appview.TID() 66 createdAt := time.Now() 67 68 putRecordResp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 69 Collection: tangled.RepoArtifactNSID, 70 Repo: user.Did, 71 Rkey: rkey, 72 Record: &lexutil.LexiconTypeDecoder{ 73 Val: &tangled.RepoArtifact{ 74 Artifact: uploadBlobResp.Blob, 75 CreatedAt: createdAt.Format(time.RFC3339), 76 Name: handler.Filename, 77 Repo: f.RepoAt.String(), 78 Tag: tag.Tag.Hash[:], 79 }, 80 }, 81 }) 82 if err != nil { 83 log.Println("failed to create record", err) 84 s.pages.Notice(w, "upload", "Failed to create artifact record. Try again later.") 85 return 86 } 87 88 log.Println(putRecordResp.Uri) 89 90 tx, err := s.db.BeginTx(r.Context(), nil) 91 if err != nil { 92 log.Println("failed to start tx") 93 s.pages.Notice(w, "upload", "Failed to create artifact. Try again later.") 94 return 95 } 96 defer tx.Rollback() 97 98 artifact := db.Artifact{ 99 Did: user.Did, 100 Rkey: rkey, 101 RepoAt: f.RepoAt, 102 Tag: tag.Tag.Hash, 103 CreatedAt: createdAt, 104 BlobCid: cid.Cid(uploadBlobResp.Blob.Ref), 105 Name: handler.Filename, 106 Size: uint64(uploadBlobResp.Blob.Size), 107 MimeType: uploadBlobResp.Blob.MimeType, 108 } 109 110 err = db.AddArtifact(tx, artifact) 111 if err != nil { 112 log.Println("failed to add artifact record to db", err) 113 s.pages.Notice(w, "upload", "Failed to create artifact. Try again later.") 114 return 115 } 116 117 err = tx.Commit() 118 if err != nil { 119 log.Println("failed to add artifact record to db") 120 s.pages.Notice(w, "upload", "Failed to create artifact. Try again later.") 121 return 122 } 123 124 s.pages.RepoArtifactFragment(w, pages.RepoArtifactParams{ 125 LoggedInUser: user, 126 RepoInfo: f.RepoInfo(s, user), 127 Artifact: artifact, 128 }) 129} 130 131// TODO: proper statuses here on early exit 132func (s *State) DownloadArtifact(w http.ResponseWriter, r *http.Request) { 133 tagParam := chi.URLParam(r, "tag") 134 filename := chi.URLParam(r, "file") 135 f, err := s.fullyResolvedRepo(r) 136 if err != nil { 137 log.Println("failed to get repo and knot", err) 138 return 139 } 140 141 tag, err := s.resolveTag(f, tagParam) 142 if err != nil { 143 log.Println("failed to resolve tag", err) 144 s.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution") 145 return 146 } 147 148 client, err := s.oauth.AuthorizedClient(r) 149 if err != nil { 150 log.Println("failed to get authorized client", err) 151 return 152 } 153 154 artifacts, err := db.GetArtifact( 155 s.db, 156 db.Filter("repo_at", f.RepoAt), 157 db.Filter("tag", tag.Tag.Hash[:]), 158 db.Filter("name", filename), 159 ) 160 if err != nil { 161 log.Println("failed to get artifacts", err) 162 return 163 } 164 if len(artifacts) != 1 { 165 log.Printf("too many or too little artifacts found") 166 return 167 } 168 169 artifact := artifacts[0] 170 171 getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did) 172 if err != nil { 173 log.Println("failed to get blob from pds", err) 174 return 175 } 176 177 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename)) 178 w.Write(getBlobResp) 179} 180 181// TODO: proper statuses here on early exit 182func (s *State) DeleteArtifact(w http.ResponseWriter, r *http.Request) { 183 user := s.oauth.GetUser(r) 184 tagParam := chi.URLParam(r, "tag") 185 filename := chi.URLParam(r, "file") 186 f, err := s.fullyResolvedRepo(r) 187 if err != nil { 188 log.Println("failed to get repo and knot", err) 189 return 190 } 191 192 client, _ := s.oauth.AuthorizedClient(r) 193 194 tag := plumbing.NewHash(tagParam) 195 196 artifacts, err := db.GetArtifact( 197 s.db, 198 db.Filter("repo_at", f.RepoAt), 199 db.Filter("tag", tag[:]), 200 db.Filter("name", filename), 201 ) 202 if err != nil { 203 log.Println("failed to get artifacts", err) 204 s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.") 205 return 206 } 207 if len(artifacts) != 1 { 208 s.pages.Notice(w, "remove", "Unable to find artifact.") 209 return 210 } 211 212 artifact := artifacts[0] 213 214 if user.Did != artifact.Did { 215 log.Println("user not authorized to delete artifact", err) 216 s.pages.Notice(w, "remove", "Unauthorized deletion of artifact.") 217 return 218 } 219 220 _, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{ 221 Collection: tangled.RepoArtifactNSID, 222 Repo: user.Did, 223 Rkey: artifact.Rkey, 224 }) 225 if err != nil { 226 log.Println("failed to get blob from pds", err) 227 s.pages.Notice(w, "remove", "Failed to remove blob from PDS.") 228 return 229 } 230 231 tx, err := s.db.BeginTx(r.Context(), nil) 232 if err != nil { 233 log.Println("failed to start tx") 234 s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.") 235 return 236 } 237 defer tx.Rollback() 238 239 err = db.DeleteArtifact(tx, 240 db.Filter("repo_at", f.RepoAt), 241 db.Filter("tag", artifact.Tag[:]), 242 db.Filter("name", filename), 243 ) 244 if err != nil { 245 log.Println("failed to remove artifact record from db", err) 246 s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.") 247 return 248 } 249 250 err = tx.Commit() 251 if err != nil { 252 log.Println("failed to remove artifact record from db") 253 s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.") 254 return 255 } 256 257 w.Write([]byte{}) 258} 259 260func (s *State) resolveTag(f *FullyResolvedRepo, tagParam string) (*types.TagReference, error) { 261 tagParam, err := url.QueryUnescape(tagParam) 262 if err != nil { 263 return nil, err 264 } 265 266 us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev) 267 if err != nil { 268 return nil, err 269 } 270 271 result, err := us.Tags(f.OwnerDid(), f.RepoName) 272 if err != nil { 273 log.Println("failed to reach knotserver", err) 274 return nil, err 275 } 276 277 var tag *types.TagReference 278 for _, t := range result.Tags { 279 if t.Tag != nil { 280 if t.Reference.Name == tagParam || t.Reference.Hash == tagParam { 281 tag = t 282 } 283 } 284 } 285 286 if tag == nil { 287 return nil, fmt.Errorf("invalid tag, only annotated tags are supported for artifacts") 288 } 289 290 if tag.Tag.Target.IsZero() { 291 return nil, fmt.Errorf("invalid tag, only annotated tags are supported for artifacts") 292 } 293 294 return tag, nil 295}