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