Skip to content

Commit 9a6532d

Browse files
committed
api: serve mbox content at advertised URLs
The API responses include mbox URLs (e.g. /api/1.4/patches/N/mbox) but no routes handle them. Register mbox endpoints for patches, covers, series and bundles under each API version prefix so that the advertised URLs work. Signed-off-by: Robin Jarry <robin@jarry.cc>
1 parent bf95161 commit 9a6532d

2 files changed

Lines changed: 188 additions & 0 deletions

File tree

‎pkg/api/api.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ func NewRouter(cfg *config.Config, database *bun.DB, baseURL string, bus db.Even
241241
registerBundleRoutes(api, h, prefix, mw)
242242
registerWebhookRoutes(api, h, prefix, mw)
243243
registerEventRoutes(api, h, prefix, mw)
244+
245+
r.Get(prefix+"/patches/{id}/mbox", h.patchMbox)
246+
r.Get(prefix+"/covers/{id}/mbox", h.coverMbox)
247+
r.Get(prefix+"/series/{id}/mbox", h.seriesMbox)
248+
r.Get(prefix+"/bundles/{id}/mbox", h.bundleMbox)
244249
}
245250

246251
latest := supportedVersions[len(supportedVersions)-1]

‎pkg/api/mbox.go‎

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Patchwork - automated patch tracking system
2+
// Copyright (C) The Patchwork Contributors (see CONTRIBUTORS)
3+
//
4+
// SPDX-License-Identifier: GPL-2.0-or-later
5+
6+
package api
7+
8+
import (
9+
"bytes"
10+
"fmt"
11+
"net/http"
12+
"strconv"
13+
14+
"github.com/go-chi/chi/v5"
15+
"github.com/uptrace/bun"
16+
17+
"github.com/getpatchwork/patchwork/pkg/db"
18+
"github.com/getpatchwork/patchwork/pkg/mbox"
19+
)
20+
21+
func (h *handler) patchMbox(w http.ResponseWriter, r *http.Request) {
22+
ctx := r.Context()
23+
q := db.GetQueries(ctx)
24+
id, err := strconv.Atoi(chi.URLParam(r, "id"))
25+
if err != nil {
26+
http.NotFound(w, r)
27+
return
28+
}
29+
30+
var patch db.Patch
31+
if err := q.DB.NewSelect().Model(&patch).
32+
Where("id = ?", id).Scan(ctx); err != nil {
33+
http.NotFound(w, r)
34+
return
35+
}
36+
37+
var project db.Project
38+
if err := q.DB.NewSelect().Model(&project).
39+
Where("id = ?", patch.ProjectID).Scan(ctx); err != nil {
40+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
41+
return
42+
}
43+
44+
sub := mbox.BuildPatchSubmission(ctx, q.DB, &patch, project.Listemail)
45+
body := mbox.Format(sub)
46+
47+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
48+
w.Header().Set("Content-Disposition",
49+
fmt.Sprintf("attachment; filename=%s.patch",
50+
mbox.SanitizeFilename(patch.Name)))
51+
_, _ = w.Write(body)
52+
}
53+
54+
func (h *handler) coverMbox(w http.ResponseWriter, r *http.Request) {
55+
ctx := r.Context()
56+
q := db.GetQueries(ctx)
57+
id, err := strconv.Atoi(chi.URLParam(r, "id"))
58+
if err != nil {
59+
http.NotFound(w, r)
60+
return
61+
}
62+
63+
var cover db.Cover
64+
if err := q.DB.NewSelect().Model(&cover).
65+
Where("id = ?", id).Scan(ctx); err != nil {
66+
http.NotFound(w, r)
67+
return
68+
}
69+
70+
var project db.Project
71+
if err := q.DB.NewSelect().Model(&project).
72+
Where("id = ?", cover.ProjectID).Scan(ctx); err != nil {
73+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
74+
return
75+
}
76+
77+
sub := mbox.BuildCoverSubmission(ctx, q.DB, &cover, project.Listemail)
78+
body := mbox.Format(sub)
79+
80+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
81+
w.Header().Set("Content-Disposition",
82+
fmt.Sprintf("attachment; filename=%s.mbox",
83+
mbox.SanitizeFilename(cover.Name)))
84+
_, _ = w.Write(body)
85+
}
86+
87+
func (h *handler) seriesMbox(w http.ResponseWriter, r *http.Request) {
88+
ctx := r.Context()
89+
q := db.GetQueries(ctx)
90+
id, err := strconv.Atoi(chi.URLParam(r, "id"))
91+
if err != nil {
92+
http.NotFound(w, r)
93+
return
94+
}
95+
96+
var series db.Series
97+
if err := q.DB.NewSelect().Model(&series).
98+
Where("id = ?", id).Scan(ctx); err != nil {
99+
http.NotFound(w, r)
100+
return
101+
}
102+
103+
var project db.Project
104+
if series.ProjectID != nil {
105+
if err := q.DB.NewSelect().Model(&project).
106+
Where("id = ?", *series.ProjectID).Scan(ctx); err != nil {
107+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
108+
return
109+
}
110+
}
111+
112+
var patches []db.Patch
113+
if err := q.DB.NewSelect().Model(&patches).
114+
Where("series_id = ?", series.ID).
115+
OrderBy("number", bun.OrderAsc).
116+
Scan(ctx); err != nil {
117+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
118+
return
119+
}
120+
121+
var parts [][]byte
122+
for i := range patches {
123+
sub := mbox.BuildPatchSubmission(ctx, q.DB, &patches[i], project.Listemail)
124+
parts = append(parts, mbox.Format(sub))
125+
}
126+
127+
name := "series"
128+
if series.Name != nil {
129+
name = *series.Name
130+
}
131+
132+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
133+
w.Header().Set("Content-Disposition",
134+
fmt.Sprintf("attachment; filename=%s.patch",
135+
mbox.SanitizeFilename(name)))
136+
_, _ = w.Write(bytes.Join(parts, []byte("\n")))
137+
}
138+
139+
func (h *handler) bundleMbox(w http.ResponseWriter, r *http.Request) {
140+
ctx := r.Context()
141+
q := db.GetQueries(ctx)
142+
id, err := strconv.Atoi(chi.URLParam(r, "id"))
143+
if err != nil {
144+
http.NotFound(w, r)
145+
return
146+
}
147+
148+
var bundle db.Bundle
149+
if err := q.DB.NewSelect().Model(&bundle).
150+
Where("id = ?", id).Scan(ctx); err != nil {
151+
http.NotFound(w, r)
152+
return
153+
}
154+
155+
var project db.Project
156+
if err := q.DB.NewSelect().Model(&project).
157+
Where("id = ?", bundle.ProjectID).Scan(ctx); err != nil {
158+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
159+
return
160+
}
161+
162+
var patches []db.Patch
163+
if err := q.DB.NewSelect().Model(&patches).
164+
Join("JOIN bundle_patch AS bp ON bp.patch_id = patch.id").
165+
Where("bp.bundle_id = ?", bundle.ID).
166+
OrderBy("bp.order", bun.OrderAsc).
167+
Scan(ctx); err != nil {
168+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
169+
return
170+
}
171+
172+
var parts [][]byte
173+
for i := range patches {
174+
sub := mbox.BuildPatchSubmission(ctx, q.DB, &patches[i], project.Listemail)
175+
parts = append(parts, mbox.Format(sub))
176+
}
177+
178+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
179+
w.Header().Set("Content-Disposition",
180+
fmt.Sprintf("attachment; filename=bundle-%d-%s.mbox",
181+
bundle.ID, mbox.SanitizeFilename(bundle.Name)))
182+
_, _ = w.Write(bytes.Join(parts, []byte("\n")))
183+
}

0 commit comments

Comments
 (0)