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