Signed-off-by: oppiliappan me@oppi.li
+40
-14
appview/repo/artifact.go
+40
-14
appview/repo/artifact.go
···
4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+
"io"
7
8
"log"
8
9
"net/http"
9
10
"net/url"
···
133
134
})
134
135
}
135
136
136
-
// TODO: proper statuses here on early exit
137
137
func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
138
-
tagParam := chi.URLParam(r, "tag")
139
-
filename := chi.URLParam(r, "file")
140
138
f, err := rp.repoResolver.Resolve(r)
141
139
if err != nil {
142
140
log.Println("failed to get repo and knot", err)
141
+
http.Error(w, "failed to resolve repo", http.StatusInternalServerError)
143
142
return
144
143
}
145
144
145
+
tagParam := chi.URLParam(r, "tag")
146
+
filename := chi.URLParam(r, "file")
147
+
146
148
tag, err := rp.resolveTag(r.Context(), f, tagParam)
147
149
if err != nil {
148
150
log.Println("failed to resolve tag", err)
···
150
152
return
151
153
}
152
154
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
155
artifacts, err := db.GetArtifact(
160
156
rp.db,
161
157
db.FilterEq("repo_at", f.RepoAt()),
···
164
160
)
165
161
if err != nil {
166
162
log.Println("failed to get artifacts", err)
163
+
http.Error(w, "failed to get artifact", http.StatusInternalServerError)
167
164
return
168
165
}
166
+
169
167
if len(artifacts) != 1 {
170
-
log.Printf("too many or too little artifacts found")
168
+
log.Printf("too many or too few artifacts found")
169
+
http.Error(w, "artifact not found", http.StatusNotFound)
171
170
return
172
171
}
173
172
174
173
artifact := artifacts[0]
175
174
176
-
getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did)
175
+
ownerPds := f.OwnerId.PDSEndpoint()
176
+
url, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob", ownerPds))
177
+
q := url.Query()
178
+
q.Set("cid", artifact.BlobCid.String())
179
+
q.Set("did", artifact.Did)
180
+
url.RawQuery = q.Encode()
181
+
182
+
req, err := http.NewRequest(http.MethodGet, url.String(), nil)
177
183
if err != nil {
178
-
log.Println("failed to get blob from pds", err)
184
+
log.Println("failed to create request", err)
185
+
http.Error(w, "failed to create request", http.StatusInternalServerError)
179
186
return
180
187
}
188
+
req.Header.Set("Content-Type", "application/json")
181
189
182
-
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
183
-
w.Write(getBlobResp)
190
+
resp, err := http.DefaultClient.Do(req)
191
+
if err != nil {
192
+
log.Println("failed to make request", err)
193
+
http.Error(w, "failed to make request to PDS", http.StatusInternalServerError)
194
+
return
195
+
}
196
+
defer resp.Body.Close()
197
+
198
+
// copy status code and relevant headers from upstream response
199
+
w.WriteHeader(resp.StatusCode)
200
+
for key, values := range resp.Header {
201
+
for _, v := range values {
202
+
w.Header().Add(key, v)
203
+
}
204
+
}
205
+
206
+
// stream the body directly to the client
207
+
if _, err := io.Copy(w, resp.Body); err != nil {
208
+
log.Println("error streaming response to client:", err)
209
+
}
184
210
}
185
211
186
212
// TODO: proper statuses here on early exit
+2
-3
appview/repo/router.go
+2
-3
appview/repo/router.go
···
21
21
r.Route("/tags", func(r chi.Router) {
22
22
r.Get("/", rp.RepoTags)
23
23
r.Route("/{tag}", func(r chi.Router) {
24
-
r.Use(middleware.AuthMiddleware(rp.oauth))
25
-
// require auth to download for now
26
24
r.Get("/download/{file}", rp.DownloadArtifact)
27
25
28
26
// require repo:push to upload or delete artifacts
···
30
28
// additionally: only the uploader can truly delete an artifact
31
29
// (record+blob will live on their pds)
32
30
r.Group(func(r chi.Router) {
33
-
r.With(mw.RepoPermissionMiddleware("repo:push"))
31
+
r.Use(middleware.AuthMiddleware(rp.oauth))
32
+
r.Use(mw.RepoPermissionMiddleware("repo:push"))
34
33
r.Post("/upload", rp.AttachArtifact)
35
34
r.Delete("/{file}", rp.DeleteArtifact)
36
35
})