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