+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"
···
134
135
})
135
136
}
136
137
137
-
// TODO: proper statuses here on early exit
138
138
func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
139
-
tagParam := chi.URLParam(r, "tag")
140
-
filename := chi.URLParam(r, "file")
141
139
f, err := rp.repoResolver.Resolve(r)
142
140
if err != nil {
143
141
log.Println("failed to get repo and knot", err)
142
+
http.Error(w, "failed to resolve repo", http.StatusInternalServerError)
144
143
return
145
144
}
145
+
146
+
tagParam := chi.URLParam(r, "tag")
147
+
filename := chi.URLParam(r, "file")
146
148
147
149
tag, err := rp.resolveTag(r.Context(), f, tagParam)
148
150
if err != nil {
···
151
153
return
152
154
}
153
155
154
-
client, err := rp.oauth.AuthorizedClient(r)
155
-
if err != nil {
156
-
log.Println("failed to get authorized client", err)
157
-
return
158
-
}
159
-
160
156
artifacts, err := db.GetArtifact(
161
157
rp.db,
162
158
db.FilterEq("repo_at", f.RepoAt()),
···
165
161
)
166
162
if err != nil {
167
163
log.Println("failed to get artifacts", err)
164
+
http.Error(w, "failed to get artifact", http.StatusInternalServerError)
168
165
return
169
166
}
167
+
170
168
if len(artifacts) != 1 {
171
-
log.Printf("too many or too little artifacts found")
169
+
log.Printf("too many or too few artifacts found")
170
+
http.Error(w, "artifact not found", http.StatusNotFound)
172
171
return
173
172
}
174
173
175
174
artifact := artifacts[0]
176
175
177
-
getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did)
176
+
ownerPds := f.OwnerId.PDSEndpoint()
177
+
url, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob", ownerPds))
178
+
q := url.Query()
179
+
q.Set("cid", artifact.BlobCid.String())
180
+
q.Set("did", artifact.Did)
181
+
url.RawQuery = q.Encode()
182
+
183
+
req, err := http.NewRequest(http.MethodGet, url.String(), nil)
184
+
if err != nil {
185
+
log.Println("failed to create request", err)
186
+
http.Error(w, "failed to create request", http.StatusInternalServerError)
187
+
return
188
+
}
189
+
req.Header.Set("Content-Type", "application/json")
190
+
191
+
resp, err := http.DefaultClient.Do(req)
178
192
if err != nil {
179
-
log.Println("failed to get blob from pds", err)
193
+
log.Println("failed to make request", err)
194
+
http.Error(w, "failed to make request to PDS", http.StatusInternalServerError)
180
195
return
181
196
}
197
+
defer resp.Body.Close()
182
198
183
-
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
184
-
w.Write(getBlobResp)
199
+
// copy status code and relevant headers from upstream response
200
+
w.WriteHeader(resp.StatusCode)
201
+
for key, values := range resp.Header {
202
+
for _, v := range values {
203
+
w.Header().Add(key, v)
204
+
}
205
+
}
206
+
207
+
// stream the body directly to the client
208
+
if _, err := io.Copy(w, resp.Body); err != nil {
209
+
log.Println("error streaming response to client:", err)
210
+
}
185
211
}
186
212
187
213
// 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
})