@vielzeug/archive 26.11.5 → 26.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_crypto.js +1 -2
- package/dist/_json.js +1 -2
- package/dist/auth/_confirmation.js +1 -2
- package/dist/auth/config.js +1 -2
- package/dist/auth/errors.js +1 -2
- package/dist/auth/store.js +1 -2
- package/dist/auth/worker.cjs +1 -1
- package/dist/auth/worker.cjs.map +1 -1
- package/dist/auth/worker.js +28 -29
- package/dist/collection.cjs +1 -1
- package/dist/collection.cjs.map +1 -1
- package/dist/collection.js +50 -51
- package/dist/contract.js +1 -2
- package/dist/errors.js +1 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +4 -4
- package/dist/worker/_pagination.js +1 -2
- package/dist/worker/_service.js +1 -2
- package/dist/worker/auth.js +1 -2
- package/dist/worker/env.js +1 -2
- package/dist/worker/http.js +1 -2
- package/dist/worker/publications.cjs +1 -1
- package/dist/worker/publications.cjs.map +1 -1
- package/dist/worker/publications.js +138 -139
- package/dist/worker/sql.js +1 -2
- package/dist/worker/worker.cjs +1 -1
- package/dist/worker/worker.cjs.map +1 -1
- package/dist/worker/worker.js +95 -96
- package/package.json +9 -9
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ARCHIVE_LIMITS as e, SESSION_MAX_AGE_SECONDS as t, archiveAliasSchema as n, archiveCollectionMetaSchema as r, archiveHealthSchema as i, archiveIdentitySchema as a, archiveLikeStateSchema as o, archiveLikedIdsSchema as s, archiveLikesPageSchema as c, archiveModerationLogSchema as l, archiveOwnReportsSchema as u, archiveReportsSchema as d, archiveSortSchema as f, isWellFormedText as p, uuidSchema as m } from "../contract.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { forwardPage as h, forwardPageParams as g, nextCursor as _ } from "./_pagination.js";
|
|
3
|
+
import { ApiError as v, badRequest as y, conflict as b, forbidden as x, invalid as S, json as C, noStore as w, notFound as T, queryParams as E, readJsonBody as D } from "./http.js";
|
|
4
|
+
import { SQL as O, likedPublicationsSql as k, likesPageSql as A, listPublicationsSql as j, moderationLogSql as M, reportsPageSql as N } from "./sql.js";
|
|
5
5
|
import { s as P } from "@vielzeug/spell";
|
|
6
|
-
|
|
6
|
+
// src/worker/publications.ts
|
|
7
7
|
async function databaseCall(e) {
|
|
8
8
|
try {
|
|
9
9
|
return await e();
|
|
10
10
|
} catch (e) {
|
|
11
|
-
throw new
|
|
11
|
+
throw new v(500, "internal", "The service is not available.", {
|
|
12
12
|
cause: e,
|
|
13
13
|
diagnostic: { kind: "database" }
|
|
14
14
|
});
|
|
@@ -18,7 +18,7 @@ var first = (e, t, ...n) => databaseCall(() => e.DB.prepare(t).bind(...n).first(
|
|
|
18
18
|
let r = await e.DB.prepare(t).bind(...n).all();
|
|
19
19
|
if (!Array.isArray(r.results)) throw Error("Missing database query results");
|
|
20
20
|
return r.results;
|
|
21
|
-
}), run = (e, t, ...n) => databaseCall(() => e.DB.prepare(t).bind(...n).run()), uuid = () => crypto.randomUUID(), now = () => (/* @__PURE__ */ new Date()).toISOString(), recordUnavailable = () => new
|
|
21
|
+
}), run = (e, t, ...n) => databaseCall(() => e.DB.prepare(t).bind(...n).run()), uuid = () => crypto.randomUUID(), now = () => (/* @__PURE__ */ new Date()).toISOString(), recordUnavailable = () => new v(503, "record-unavailable", "The record is not available.", { diagnostic: { kind: "record" } }), F = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, rowToEntry = (e, t) => {
|
|
22
22
|
let n;
|
|
23
23
|
try {
|
|
24
24
|
n = JSON.parse(t.payload_json);
|
|
@@ -51,11 +51,11 @@ var first = (e, t, ...n) => databaseCall(() => e.DB.prepare(t).bind(...n).first(
|
|
|
51
51
|
hidden: t.hidden_at !== null
|
|
52
52
|
};
|
|
53
53
|
}, entryById = async (e, t, n) => {
|
|
54
|
-
let r = await first(t,
|
|
54
|
+
let r = await first(t, O.publication, n, e.name);
|
|
55
55
|
return r === null ? null : rowToEntry(e, r);
|
|
56
56
|
}, searchMatchFor = (t) => {
|
|
57
57
|
if (t === null) return;
|
|
58
|
-
if (t.length > e.searchChars) throw
|
|
58
|
+
if (t.length > e.searchChars) throw y();
|
|
59
59
|
let n = (t.match(/[\p{L}\p{N}]+/gu) ?? []).slice(0, e.searchTerms);
|
|
60
60
|
if (n.length !== 0) return n.map((e) => `"${e}"*`).join(" ");
|
|
61
61
|
}, entryCursorContext = (e) => JSON.stringify([
|
|
@@ -71,13 +71,13 @@ var first = (e, t, ...n) => databaseCall(() => e.DB.prepare(t).bind(...n).first(
|
|
|
71
71
|
likeCount: e.like_count
|
|
72
72
|
}), I = 16, vocabValues = (e, t, n) => {
|
|
73
73
|
let r = e.getAll(t);
|
|
74
|
-
if (r.length > I) throw
|
|
74
|
+
if (r.length > I) throw y();
|
|
75
75
|
let i = [...new Set(r)].sort();
|
|
76
|
-
if (i.some((e) => !n.includes(e))) throw
|
|
76
|
+
if (i.some((e) => !n.includes(e))) throw y();
|
|
77
77
|
return i;
|
|
78
78
|
};
|
|
79
79
|
async function listEntries(e, t, n) {
|
|
80
|
-
let r = new URL(t.url), i =
|
|
80
|
+
let r = new URL(t.url), i = E(r, [
|
|
81
81
|
"author",
|
|
82
82
|
"cursor",
|
|
83
83
|
"filter",
|
|
@@ -86,9 +86,9 @@ async function listEntries(e, t, n) {
|
|
|
86
86
|
"sort",
|
|
87
87
|
"tag"
|
|
88
88
|
], ["filter", "tag"]), a = i.get("sort") ?? "recent", o = f.safeParse(a);
|
|
89
|
-
if (!o.success) throw
|
|
89
|
+
if (!o.success) throw y();
|
|
90
90
|
let s = vocabValues(i, "filter", e.filters), c = vocabValues(i, "tag", e.tags), l = i.get("author");
|
|
91
|
-
if (l !== null && !F.test(l)) throw
|
|
91
|
+
if (l !== null && !F.test(l)) throw y();
|
|
92
92
|
let u = searchMatchFor(i.get("search")), d = entryCursorContext({
|
|
93
93
|
author: l ?? void 0,
|
|
94
94
|
collection: e.name,
|
|
@@ -96,7 +96,7 @@ async function listEntries(e, t, n) {
|
|
|
96
96
|
searchMatch: u,
|
|
97
97
|
sort: o.data,
|
|
98
98
|
tags: c
|
|
99
|
-
}), { cursor: p, limit: m } =
|
|
99
|
+
}), { cursor: p, limit: m } = g(i, d), h = {
|
|
100
100
|
author: l ?? void 0,
|
|
101
101
|
collection: e.name,
|
|
102
102
|
cursor: p === null ? void 0 : {
|
|
@@ -109,11 +109,11 @@ async function listEntries(e, t, n) {
|
|
|
109
109
|
searchMatch: u,
|
|
110
110
|
sort: o.data,
|
|
111
111
|
tags: c
|
|
112
|
-
},
|
|
113
|
-
cursor:
|
|
114
|
-
entries:
|
|
112
|
+
}, v = j(h), b = await all(n, v.sql, ...v.parameters), x = b.slice(0, m), S = x.at(-1), T = {
|
|
113
|
+
cursor: b.length > m && S !== void 0 ? _(cursorFromRow(S), d) : null,
|
|
114
|
+
entries: x.map((t) => rowToEntry(e, t))
|
|
115
115
|
};
|
|
116
|
-
return
|
|
116
|
+
return C(200, T, w);
|
|
117
117
|
}
|
|
118
118
|
function collectionMeta(e) {
|
|
119
119
|
let t = r.safeParse({
|
|
@@ -122,17 +122,17 @@ function collectionMeta(e) {
|
|
|
122
122
|
tags: [...e.tags].sort()
|
|
123
123
|
});
|
|
124
124
|
if (!t.success) throw recordUnavailable();
|
|
125
|
-
return
|
|
125
|
+
return C(200, t.data, w);
|
|
126
126
|
}
|
|
127
127
|
async function getEntry(e, t, n) {
|
|
128
128
|
let r = await entryById(e, t, n);
|
|
129
|
-
if (r === null) throw
|
|
130
|
-
return
|
|
129
|
+
if (r === null) throw T();
|
|
130
|
+
return C(200, r, w);
|
|
131
131
|
}
|
|
132
132
|
async function healthJson(t, n) {
|
|
133
133
|
let r = null, a = null;
|
|
134
134
|
try {
|
|
135
|
-
r = (await first(n,
|
|
135
|
+
r = (await first(n, O.schemaVersion))?.applied ?? 0;
|
|
136
136
|
} catch {
|
|
137
137
|
a = "database-unavailable";
|
|
138
138
|
}
|
|
@@ -149,12 +149,12 @@ async function healthJson(t, n) {
|
|
|
149
149
|
status: a === null ? "ok" : "degraded"
|
|
150
150
|
});
|
|
151
151
|
if (!o.success) throw recordUnavailable();
|
|
152
|
-
return
|
|
152
|
+
return C(a === null ? 200 : 503, o.data, w);
|
|
153
153
|
}
|
|
154
154
|
var identityJson = (e, t) => {
|
|
155
155
|
let n = a.safeParse(t);
|
|
156
156
|
if (!n.success) throw recordUnavailable();
|
|
157
|
-
return
|
|
157
|
+
return C(e, n.data, w);
|
|
158
158
|
};
|
|
159
159
|
function getMe(e) {
|
|
160
160
|
return identityJson(200, e?.publisher ? {
|
|
@@ -164,18 +164,18 @@ function getMe(e) {
|
|
|
164
164
|
} : null);
|
|
165
165
|
}
|
|
166
166
|
async function registerMe(e, t, n) {
|
|
167
|
-
let r = await
|
|
168
|
-
if (!i.success) throw
|
|
169
|
-
let a = i.data.name ?? "", o = await first(t,
|
|
167
|
+
let r = await D(e), i = L.safeParse(r);
|
|
168
|
+
if (!i.success) throw S();
|
|
169
|
+
let a = i.data.name ?? "", o = await first(t, O.publisher, n.subject.issuer, n.subject.subject);
|
|
170
170
|
if (o !== null) return identityJson(200, {
|
|
171
171
|
id: o.id,
|
|
172
172
|
name: o.display_name,
|
|
173
173
|
operator: n.operator
|
|
174
174
|
});
|
|
175
175
|
let s = uuid();
|
|
176
|
-
await run(t,
|
|
177
|
-
let c = await first(t,
|
|
178
|
-
if (c === null) throw await first(t,
|
|
176
|
+
await run(t, O.insertPublisher, s, n.subject.issuer, n.subject.subject, a, now(), n.subject.issuer, n.subject.subject, Date.now());
|
|
177
|
+
let c = await first(t, O.publisher, n.subject.issuer, n.subject.subject);
|
|
178
|
+
if (c === null) throw await first(t, O.revokedSubject, n.subject.issuer, n.subject.subject, Date.now()) === null ? b() : new v(401, "unauthenticated", "Sign-in is required.");
|
|
179
179
|
return identityJson(201, {
|
|
180
180
|
id: c.id,
|
|
181
181
|
name: c.display_name,
|
|
@@ -183,37 +183,37 @@ async function registerMe(e, t, n) {
|
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
async function renameMe(e, t, n) {
|
|
186
|
-
let r = requirePublisherRow(n), i = await
|
|
187
|
-
if (!a.success) throw
|
|
188
|
-
return a.data.name !== r.displayName && await run(t,
|
|
186
|
+
let r = requirePublisherRow(n), i = await D(e), a = R.safeParse(i);
|
|
187
|
+
if (!a.success) throw S();
|
|
188
|
+
return a.data.name !== r.displayName && await run(t, O.renamePublisher, a.data.name, r.id, a.data.name), identityJson(200, {
|
|
189
189
|
id: r.id,
|
|
190
190
|
name: a.data.name,
|
|
191
191
|
operator: n.operator
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
194
|
async function deleteProfile(e, t) {
|
|
195
|
-
return t.publisher !== null && await run(e,
|
|
195
|
+
return t.publisher !== null && await run(e, O.deletePublisher, t.publisher.id), new Response(null, {
|
|
196
196
|
headers: { "cache-control": "no-store" },
|
|
197
197
|
status: 204
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
200
|
async function deleteAccountProfile(e, n) {
|
|
201
201
|
let { issuer: r, subject: i } = n.subject;
|
|
202
|
-
return await databaseCall(() => e.DB.batch([e.DB.prepare(
|
|
203
|
-
headers:
|
|
202
|
+
return await databaseCall(() => e.DB.batch([e.DB.prepare(O.revokeSubject).bind(r, i, Date.now() + (t + 10) * 1e3), e.DB.prepare(O.deleteSubjectPublisher).bind(r, i)])), new Response(null, {
|
|
203
|
+
headers: w,
|
|
204
204
|
status: 204
|
|
205
205
|
});
|
|
206
206
|
}
|
|
207
207
|
async function erasePublisher(e, t, n) {
|
|
208
208
|
let r = requireOperatorRow(t);
|
|
209
|
-
return await databaseCall(() => e.DB.batch([e.DB.prepare(
|
|
210
|
-
headers:
|
|
209
|
+
return await databaseCall(() => e.DB.batch([e.DB.prepare(O.auditErasure).bind(uuid(), r.id, now(), n), e.DB.prepare(O.deletePublisher).bind(n)])), new Response(null, {
|
|
210
|
+
headers: w,
|
|
211
211
|
status: 204
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
214
|
async function listModerationLog(e, t, n) {
|
|
215
215
|
requireOperatorRow(n);
|
|
216
|
-
let r = "moderation-log", { cursor: i, limit: a } =
|
|
216
|
+
let r = "moderation-log", { cursor: i, limit: a } = h(e, r), o = M({
|
|
217
217
|
cursor: i,
|
|
218
218
|
limit: a + 1
|
|
219
219
|
}), s = await all(t, o.sql, ...o.parameters), c = s.slice(0, a), u = c.at(-1), d = l.safeParse({
|
|
@@ -225,55 +225,55 @@ async function listModerationLog(e, t, n) {
|
|
|
225
225
|
operatorId: e.operator_id,
|
|
226
226
|
targetId: e.target_id
|
|
227
227
|
})),
|
|
228
|
-
cursor: s.length > a && u !== void 0 ?
|
|
228
|
+
cursor: s.length > a && u !== void 0 ? _({
|
|
229
229
|
createdAt: u.created_at,
|
|
230
230
|
id: u.id
|
|
231
231
|
}, r) : null
|
|
232
232
|
});
|
|
233
233
|
if (!d.success) throw recordUnavailable();
|
|
234
|
-
return
|
|
234
|
+
return C(200, d.data, w);
|
|
235
235
|
}
|
|
236
236
|
var requirePublisherRow = (e) => {
|
|
237
|
-
if (e.publisher === null) throw
|
|
237
|
+
if (e.publisher === null) throw x("Register before publishing.");
|
|
238
238
|
return e.publisher;
|
|
239
239
|
}, requireOperatorRow = (e) => {
|
|
240
|
-
if (!e.operator) throw
|
|
241
|
-
if (e.publisher === null) throw
|
|
240
|
+
if (!e.operator) throw x("Operator access is required.");
|
|
241
|
+
if (e.publisher === null) throw x("Register before moderating.");
|
|
242
242
|
return e.publisher;
|
|
243
243
|
}, L = P.object({ name: n.optional() }), R = P.object({ name: n });
|
|
244
244
|
async function myPublications(t, n, r) {
|
|
245
|
-
if (r.publisher === null) return
|
|
246
|
-
let i = await all(n,
|
|
247
|
-
return
|
|
245
|
+
if (r.publisher === null) return C(200, { entries: [] }, w);
|
|
246
|
+
let i = await all(n, O.ownPublications, r.publisher.id, t.name, e.maxPublications);
|
|
247
|
+
return C(200, { entries: i.map((e) => rowToOwnedEntry(t, e)) }, w);
|
|
248
248
|
}
|
|
249
249
|
async function myLikes(t, n, r) {
|
|
250
250
|
let i = new URL(t.url).searchParams.get("ids");
|
|
251
251
|
if (i === null) {
|
|
252
|
-
let e = `likes:${r.publisher?.id ?? "unregistered"}`, { cursor: i, limit: a } =
|
|
253
|
-
if (r.publisher === null) return
|
|
252
|
+
let e = `likes:${r.publisher?.id ?? "unregistered"}`, { cursor: i, limit: a } = h(t, e);
|
|
253
|
+
if (r.publisher === null) return C(200, {
|
|
254
254
|
cursor: null,
|
|
255
255
|
ids: []
|
|
256
|
-
},
|
|
257
|
-
let o =
|
|
256
|
+
}, w);
|
|
257
|
+
let o = A(r.publisher.id, {
|
|
258
258
|
cursor: i,
|
|
259
259
|
limit: a + 1
|
|
260
260
|
}), s = await all(n, o.sql, ...o.parameters), l = s.slice(0, a), u = l.at(-1), d = c.safeParse({
|
|
261
|
-
cursor: s.length > a && u !== void 0 ?
|
|
261
|
+
cursor: s.length > a && u !== void 0 ? _({
|
|
262
262
|
createdAt: u.created_at,
|
|
263
263
|
id: u.publication_id
|
|
264
264
|
}, e) : null,
|
|
265
265
|
ids: l.map((e) => e.publication_id)
|
|
266
266
|
});
|
|
267
267
|
if (!d.success) throw recordUnavailable();
|
|
268
|
-
return
|
|
268
|
+
return C(200, d.data, w);
|
|
269
269
|
}
|
|
270
|
-
|
|
270
|
+
E(new URL(t.url), ["ids"]);
|
|
271
271
|
let a = [...new Set(i.split(","))];
|
|
272
|
-
if (a.length > e.likeStateIds || a.some((e) => !F.test(e))) throw
|
|
273
|
-
if (r.publisher === null) return
|
|
274
|
-
let o =
|
|
272
|
+
if (a.length > e.likeStateIds || a.some((e) => !F.test(e))) throw y();
|
|
273
|
+
if (r.publisher === null) return C(200, { ids: [] }, w);
|
|
274
|
+
let o = k(r.publisher.id, a), l = await all(n, o.sql, ...o.parameters), u = new Set(l.map((e) => e.publication_id)), d = { ids: a.filter((e) => u.has(e)) }, f = s.safeParse(d);
|
|
275
275
|
if (!f.success) throw recordUnavailable();
|
|
276
|
-
return
|
|
276
|
+
return C(200, f.data, w);
|
|
277
277
|
}
|
|
278
278
|
var z = P.object({
|
|
279
279
|
clientKey: m,
|
|
@@ -284,135 +284,135 @@ var z = P.object({
|
|
|
284
284
|
});
|
|
285
285
|
async function resolveRetryRecord(e, t, n, r) {
|
|
286
286
|
if (n === null || n.expires_at <= Date.now()) return null;
|
|
287
|
-
if (n.payload_hash !== r) throw
|
|
288
|
-
if (n.publication_id === null) throw new
|
|
287
|
+
if (n.payload_hash !== r) throw b("That key was used for a different payload.");
|
|
288
|
+
if (n.publication_id === null) throw new v(410, "gone", "That publication was removed.");
|
|
289
289
|
let i = await entryById(e, t, n.publication_id);
|
|
290
|
-
return i === null ? "vanished" :
|
|
290
|
+
return i === null ? "vanished" : C(200, i, w);
|
|
291
291
|
}
|
|
292
292
|
async function publishEntry(t, n, r, i, a) {
|
|
293
|
-
if (!i.publishingEnabled) throw new
|
|
294
|
-
let o = requirePublisherRow(a), s = await
|
|
295
|
-
if (!c.success) throw
|
|
293
|
+
if (!i.publishingEnabled) throw new v(503, "publishing-disabled", "Publishing is currently disabled.");
|
|
294
|
+
let o = requirePublisherRow(a), s = await D(n), c = z.safeParse(s);
|
|
295
|
+
if (!c.success) throw S();
|
|
296
296
|
let { clientKey: l } = c.data, u = await t.derive(c.data.payload);
|
|
297
|
-
if (u === null) throw
|
|
298
|
-
let d = u.canonicalJson, f = u.contentHash, p = o.id, m = t.name,
|
|
299
|
-
if (
|
|
300
|
-
let
|
|
301
|
-
if (
|
|
302
|
-
if (
|
|
303
|
-
if (
|
|
304
|
-
return
|
|
297
|
+
if (u === null) throw S();
|
|
298
|
+
let d = u.canonicalJson, f = u.contentHash, p = o.id, m = t.name, h = await resolveRetryRecord(t, r, await first(r, O.request, p, m, l), f);
|
|
299
|
+
if (h !== null && h !== "vanished") return h;
|
|
300
|
+
let g = await first(r, O.publicationByKey, p, m, l);
|
|
301
|
+
if (g !== null) {
|
|
302
|
+
if (g.hidden_at !== null) throw x("This entry is under moderation review.");
|
|
303
|
+
if (g.content_hash !== f) throw b("That key was used for a different payload.");
|
|
304
|
+
return C(200, rowToEntry(t, g), w);
|
|
305
305
|
}
|
|
306
|
-
let
|
|
306
|
+
let _ = now(), y = Date.now() + e.idempotencyRetentionMs, T = JSON.stringify(u.tags);
|
|
307
307
|
if (((await databaseCall(() => r.DB.batch([
|
|
308
|
-
r.DB.prepare(
|
|
309
|
-
r.DB.prepare(
|
|
310
|
-
r.DB.prepare(
|
|
308
|
+
r.DB.prepare(O.expireRequests).bind(Date.now()),
|
|
309
|
+
r.DB.prepare(O.insertPublication).bind(uuid(), p, m, l, 1, u.contentVersion, d, f, u.filter, u.title, T, _, _, p, m, e.maxPublications, p, m, l, Date.now()),
|
|
310
|
+
r.DB.prepare(O.insertRequest).bind(p, m, l, f, _, y, p, m, l, f)
|
|
311
311
|
])))[1]?.meta?.changes ?? 0) === 0) {
|
|
312
|
-
let e = await resolveRetryRecord(t, r, await first(r,
|
|
313
|
-
if (e === "vanished") throw new
|
|
312
|
+
let e = await resolveRetryRecord(t, r, await first(r, O.request, p, m, l), f);
|
|
313
|
+
if (e === "vanished") throw new v(410, "gone", "That publication was removed.");
|
|
314
314
|
if (e !== null) return e;
|
|
315
|
-
let n = await first(r,
|
|
316
|
-
if (n !== null && n.hidden_at !== null) throw
|
|
317
|
-
if (n !== null && n.content_hash === f) return
|
|
318
|
-
throw n === null ? new
|
|
315
|
+
let n = await first(r, O.publicationByKey, p, m, l);
|
|
316
|
+
if (n !== null && n.hidden_at !== null) throw x("This entry is under moderation review.");
|
|
317
|
+
if (n !== null && n.content_hash === f) return C(200, rowToEntry(t, n), w);
|
|
318
|
+
throw n === null ? new v(429, "quota", "Publication quota reached.") : b("That key was used for a different payload.");
|
|
319
319
|
}
|
|
320
|
-
let
|
|
321
|
-
if (
|
|
322
|
-
return
|
|
320
|
+
let E = await first(r, O.publicationByKey, p, m, l);
|
|
321
|
+
if (E === null) throw b();
|
|
322
|
+
return C(201, rowToEntry(t, E), w);
|
|
323
323
|
}
|
|
324
324
|
async function updateEntry(e, t, n, r, i, a) {
|
|
325
|
-
if (!r.publishingEnabled) throw new
|
|
326
|
-
let o = requirePublisherRow(i), s = await first(n,
|
|
327
|
-
if (s === null || s.owner_id !== o.id) throw
|
|
328
|
-
if (s.hidden_at !== null) throw
|
|
329
|
-
let c = await
|
|
330
|
-
if (!l.success) throw
|
|
325
|
+
if (!r.publishingEnabled) throw new v(503, "publishing-disabled", "Publishing is currently disabled.");
|
|
326
|
+
let o = requirePublisherRow(i), s = await first(n, O.publicationAny, a, e.name);
|
|
327
|
+
if (s === null || s.owner_id !== o.id) throw T();
|
|
328
|
+
if (s.hidden_at !== null) throw x("This entry is under moderation review.");
|
|
329
|
+
let c = await D(t), l = B.safeParse(c);
|
|
330
|
+
if (!l.success) throw S();
|
|
331
331
|
let { expectedRevision: u } = l.data;
|
|
332
|
-
if (u !== s.revision) throw
|
|
332
|
+
if (u !== s.revision) throw b();
|
|
333
333
|
let d = await e.derive(l.data.payload);
|
|
334
|
-
if (d === null) throw
|
|
334
|
+
if (d === null) throw S();
|
|
335
335
|
let f = d.canonicalJson, p = d.contentHash;
|
|
336
336
|
if (p === s.content_hash) {
|
|
337
|
-
let t = await first(n,
|
|
338
|
-
if (t === null || t.owner_id !== o.id) throw
|
|
339
|
-
if (t.hidden_at !== null) throw
|
|
340
|
-
if (t.revision !== u) throw
|
|
341
|
-
return
|
|
337
|
+
let t = await first(n, O.publicationAny, a, e.name);
|
|
338
|
+
if (t === null || t.owner_id !== o.id) throw T();
|
|
339
|
+
if (t.hidden_at !== null) throw x("This entry is under moderation review.");
|
|
340
|
+
if (t.revision !== u) throw b();
|
|
341
|
+
return C(200, rowToEntry(e, t), w);
|
|
342
342
|
}
|
|
343
343
|
let m = JSON.stringify(d.tags);
|
|
344
|
-
if (((await run(n,
|
|
345
|
-
let t = await first(n,
|
|
346
|
-
throw t === null ?
|
|
344
|
+
if (((await run(n, O.updatePublication, 1, d.contentVersion, f, p, d.filter, d.title, m, now(), a, o.id, u, p)).meta?.changes ?? 0) === 0) {
|
|
345
|
+
let t = await first(n, O.publicationAny, a, e.name);
|
|
346
|
+
throw t === null ? T() : t.hidden_at === null ? b() : x("This entry is under moderation review.");
|
|
347
347
|
}
|
|
348
|
-
let
|
|
349
|
-
if (
|
|
350
|
-
return
|
|
348
|
+
let h = await entryById(e, n, a);
|
|
349
|
+
if (h === null) throw T();
|
|
350
|
+
return C(200, h, w);
|
|
351
351
|
}
|
|
352
352
|
async function deleteEntry(e, t, n, r) {
|
|
353
353
|
if (n.operator) {
|
|
354
354
|
let i = requireOperatorRow(n);
|
|
355
|
-
await databaseCall(() => t.DB.batch([t.DB.prepare(
|
|
355
|
+
await databaseCall(() => t.DB.batch([t.DB.prepare(O.auditRemoval).bind(uuid(), i.id, now(), r, e.name), t.DB.prepare(O.removePublicationAny).bind(r, e.name)]));
|
|
356
356
|
} else {
|
|
357
|
-
let i = requirePublisherRow(n), a = await first(t,
|
|
357
|
+
let i = requirePublisherRow(n), a = await first(t, O.publicationAny, r, e.name);
|
|
358
358
|
if (a === null) return new Response(null, {
|
|
359
|
-
headers:
|
|
359
|
+
headers: w,
|
|
360
360
|
status: 204
|
|
361
361
|
});
|
|
362
|
-
if (a.owner_id !== i.id) throw
|
|
363
|
-
await run(t,
|
|
362
|
+
if (a.owner_id !== i.id) throw T();
|
|
363
|
+
await run(t, O.removePublication, r, i.id);
|
|
364
364
|
}
|
|
365
365
|
return new Response(null, {
|
|
366
|
-
headers:
|
|
366
|
+
headers: w,
|
|
367
367
|
status: 204
|
|
368
368
|
});
|
|
369
369
|
}
|
|
370
370
|
async function setLike(e, t, n, r, i, a) {
|
|
371
371
|
let s = requirePublisherRow(r);
|
|
372
|
-
if (a && !n.likesEnabled) throw new
|
|
373
|
-
if (await first(t,
|
|
374
|
-
a ? await run(t,
|
|
375
|
-
let c = await first(t,
|
|
376
|
-
if (c === null) throw
|
|
372
|
+
if (a && !n.likesEnabled) throw new v(503, "likes-disabled", "Likes are currently disabled.");
|
|
373
|
+
if (await first(t, O.publication, i, e.name) === null) throw T();
|
|
374
|
+
a ? await run(t, O.insertLike, s.id, now(), i) : await run(t, O.removeLike, i, s.id);
|
|
375
|
+
let c = await first(t, O.likeState, s.id, i);
|
|
376
|
+
if (c === null) throw T();
|
|
377
377
|
let l = {
|
|
378
378
|
id: c.id,
|
|
379
379
|
likeCount: c.like_count,
|
|
380
380
|
liked: c.liked === 1
|
|
381
381
|
}, u = o.safeParse(l);
|
|
382
382
|
if (!u.success) throw recordUnavailable();
|
|
383
|
-
return
|
|
383
|
+
return C(200, u.data, w);
|
|
384
384
|
}
|
|
385
385
|
var V = P.object({ reason: P.string().check(p).trim().min(1).max(e.reportReasonChars) });
|
|
386
386
|
async function reportEntry(e, t, n, r, i) {
|
|
387
|
-
let a = requirePublisherRow(r), o = await
|
|
388
|
-
if (!s.success) throw
|
|
389
|
-
if (await first(n,
|
|
390
|
-
return await run(n,
|
|
391
|
-
headers:
|
|
387
|
+
let a = requirePublisherRow(r), o = await D(t), s = V.safeParse(o);
|
|
388
|
+
if (!s.success) throw S();
|
|
389
|
+
if (await first(n, O.publication, i, e.name) === null) throw T();
|
|
390
|
+
return await run(n, O.insertReport, uuid(), a.id, s.data.reason, now(), i, e.name), new Response(null, {
|
|
391
|
+
headers: w,
|
|
392
392
|
status: 204
|
|
393
393
|
});
|
|
394
394
|
}
|
|
395
395
|
async function setHidden(e, t, n, r, i) {
|
|
396
396
|
let a = requireOperatorRow(n);
|
|
397
|
-
if (await first(t,
|
|
398
|
-
let o = i ? t.DB.prepare(
|
|
399
|
-
return await databaseCall(() => t.DB.batch([t.DB.prepare(
|
|
400
|
-
headers:
|
|
397
|
+
if (await first(t, O.publicationAny, r, e.name) === null) throw T();
|
|
398
|
+
let o = i ? t.DB.prepare(O.hidePublication).bind(now(), r, e.name) : t.DB.prepare(O.unhidePublication).bind(r, e.name);
|
|
399
|
+
return await databaseCall(() => t.DB.batch([t.DB.prepare(O.auditHide).bind(uuid(), a.id, i ? "hide" : "unhide", now(), r, e.name, Number(i)), o])), new Response(null, {
|
|
400
|
+
headers: w,
|
|
401
401
|
status: 204
|
|
402
402
|
});
|
|
403
403
|
}
|
|
404
404
|
async function listReports(e, t, n, r) {
|
|
405
405
|
requireOperatorRow(r);
|
|
406
406
|
let i = new URL(t.url).searchParams.get("status") ?? "unresolved";
|
|
407
|
-
if (i !== "unresolved" && i !== "resolved") throw
|
|
408
|
-
let a = `reports:${e.name}:${i}`, { cursor: o, limit: s } =
|
|
407
|
+
if (i !== "unresolved" && i !== "resolved") throw y();
|
|
408
|
+
let a = `reports:${e.name}:${i}`, { cursor: o, limit: s } = h(t, a, ["status"]), c = N({
|
|
409
409
|
collection: e.name,
|
|
410
410
|
resolved: i === "resolved"
|
|
411
411
|
}, {
|
|
412
412
|
cursor: o,
|
|
413
413
|
limit: s + 1
|
|
414
414
|
}), l = await all(n, c.sql, ...c.parameters), u = l.slice(0, s), f = u.at(-1), p = d.safeParse({
|
|
415
|
-
cursor: l.length > s && f !== void 0 ?
|
|
415
|
+
cursor: l.length > s && f !== void 0 ? _({
|
|
416
416
|
createdAt: f.created_at,
|
|
417
417
|
id: f.id
|
|
418
418
|
}, a) : null,
|
|
@@ -430,26 +430,26 @@ async function listReports(e, t, n, r) {
|
|
|
430
430
|
}))
|
|
431
431
|
});
|
|
432
432
|
if (!p.success) throw recordUnavailable();
|
|
433
|
-
return
|
|
433
|
+
return C(200, p.data, w);
|
|
434
434
|
}
|
|
435
435
|
async function resolveReport(e, t, n, r) {
|
|
436
|
-
if (requireOperatorRow(n), await first(t,
|
|
437
|
-
return await run(t,
|
|
438
|
-
headers:
|
|
436
|
+
if (requireOperatorRow(n), await first(t, O.reportById, r, e.name) === null) throw T();
|
|
437
|
+
return await run(t, O.resolveReport, now(), r, e.name), new Response(null, {
|
|
438
|
+
headers: w,
|
|
439
439
|
status: 204
|
|
440
440
|
});
|
|
441
441
|
}
|
|
442
442
|
async function myReports(e, t, n) {
|
|
443
|
-
let r = `own-reports:${n.publisher?.id ?? "unregistered"}`, { cursor: i, limit: a } =
|
|
444
|
-
if (n.publisher === null) return
|
|
443
|
+
let r = `own-reports:${n.publisher?.id ?? "unregistered"}`, { cursor: i, limit: a } = h(e, r);
|
|
444
|
+
if (n.publisher === null) return C(200, {
|
|
445
445
|
cursor: null,
|
|
446
446
|
reports: []
|
|
447
|
-
},
|
|
448
|
-
let o =
|
|
447
|
+
}, w);
|
|
448
|
+
let o = N({ publisherId: n.publisher.id }, {
|
|
449
449
|
cursor: i,
|
|
450
450
|
limit: a + 1
|
|
451
451
|
}), s = await all(t, o.sql, ...o.parameters), c = s.slice(0, a), l = c.at(-1), d = u.safeParse({
|
|
452
|
-
cursor: s.length > a && l !== void 0 ?
|
|
452
|
+
cursor: s.length > a && l !== void 0 ? _({
|
|
453
453
|
createdAt: l.created_at,
|
|
454
454
|
id: l.id
|
|
455
455
|
}, r) : null,
|
|
@@ -463,9 +463,8 @@ async function myReports(e, t, n) {
|
|
|
463
463
|
}))
|
|
464
464
|
});
|
|
465
465
|
if (!d.success) throw recordUnavailable();
|
|
466
|
-
return
|
|
466
|
+
return C(200, d.data, w);
|
|
467
467
|
}
|
|
468
|
-
//#endregion
|
|
469
468
|
export { collectionMeta, deleteAccountProfile, deleteEntry, deleteProfile, erasePublisher, getEntry, getMe, healthJson, listEntries, listModerationLog, listReports, myLikes, myPublications, myReports, publishEntry, registerMe, renameMe, reportEntry, requireOperatorRow, requirePublisherRow, resolveReport, setHidden, setLike, updateEntry };
|
|
470
469
|
|
|
471
470
|
//# sourceMappingURL=publications.js.map
|
package/dist/worker/sql.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/worker/sql.ts
|
|
2
2
|
var e = {
|
|
3
3
|
auditErasure: "INSERT INTO moderation_actions(id, operator_id, action, collection, target_id, created_at)\n SELECT ?, ?, 'erase-publisher', NULL, id, ? FROM publishers WHERE id = ?",
|
|
4
4
|
auditHide: "INSERT INTO moderation_actions(id, operator_id, action, collection, target_id, created_at)\n SELECT ?, ?, ?, collection, id, ? FROM publications\n WHERE id = ? AND collection = ? AND (hidden_at IS NOT NULL) <> ?",
|
|
@@ -98,7 +98,6 @@ function likedPublicationsSql(e, t) {
|
|
|
98
98
|
AND publication_id IN (${t.map(() => "?").join(", ") || "NULL"})`
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
-
//#endregion
|
|
102
101
|
export { e as SQL, likedPublicationsSql, likesPageSql, listPublicationsSql, moderationLogSql, reportsPageSql };
|
|
103
102
|
|
|
104
103
|
//# sourceMappingURL=sql.js.map
|
package/dist/worker/worker.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../contract.cjs"),t=require("./http.cjs"),n=require("../errors.cjs"),r=require("./sql.cjs"),i=require("./auth.cjs"),a=require("./env.cjs"),o=require("./publications.cjs");var s=[{id:`accountErase`,label:`/me/account`,methods:[`DELETE`],pattern:/^\/api\/v1\/me\/account$/},{id:`health`,label:`/health`,methods:[`GET`],pattern:/^\/api\/v1\/health$/},{id:`signOut`,label:`/sign-out`,methods:[`POST`],pattern:/^\/api\/v1\/sign-out$/},{id:`resolveReport`,label:`/collections/:collection/reports/:id/resolved`,methods:[`PUT`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports\/([0-9a-f-]{36})\/resolved$/i},{id:`meta`,label:`/collections/:collection`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)$/},{id:`reports`,label:`/collections/:collection/reports`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports$/},{id:`entries`,label:`/collections/:collection/entries`,methods:[`GET`,`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries$/},{id:`entry`,label:`/collections/:collection/entries/:id`,methods:[`GET`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})$/i},{id:`like`,label:`/collections/:collection/entries/:id/like`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/like$/i},{id:`report`,label:`/collections/:collection/entries/:id/report`,methods:[`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/report$/i},{id:`hidden`,label:`/collections/:collection/entries/:id/hidden`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/hidden$/i},{id:`modLog`,label:`/moderation-log`,methods:[`GET`],pattern:/^\/api\/v1\/moderation-log$/},{id:`publisherErase`,label:`/publishers/:id`,methods:[`DELETE`],pattern:/^\/api\/v1\/publishers\/([0-9a-f-]{36})$/i},{id:`me`,label:`/me`,methods:[`GET`,`POST`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/me$/},{id:`mePublications`,label:`/me/publications`,methods:[`GET`],pattern:/^\/api\/v1\/me\/publications$/},{id:`meLikes`,label:`/me/likes`,methods:[`GET`],pattern:/^\/api\/v1\/me\/likes$/},{id:`meReports`,label:`/me/reports`,methods:[`GET`],pattern:/^\/api\/v1\/me\/reports$/}],matchRoute=e=>{for(let t of s){let n=e.match(t.pattern);if(n)return{id:t.id,label:t.label,methods:t.methods,segments:n.slice(1)}}return null},routeLabel=e=>matchRoute(e)?.label??`unknown`,c=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,l=new Set([`entry`,`like`,`report`,`hidden`,`resolveReport`]),notAllowed=()=>new t.ApiError(405,`method-not-allowed`,`That method is not supported here.`),u=new WeakMap,rateLimitHash=async(e,t)=>{let n=u.get(e);n===void 0&&(n=crypto.subtle.importKey(`raw`,new TextEncoder().encode(e.rateLimitPepper),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),u.set(e,n));let r=await crypto.subtle.sign(`HMAC`,await n,new TextEncoder().encode(t));return[...new Uint8Array(r)].map(e=>e.toString(16).padStart(2,`0`)).join(``)},d=60;async function readLimit(e,n,r){let i=r.headers.get(`cf-connecting-ip`)??(e.appEnv===`local`?`127.0.0.1`:null);if(i===null)throw new t.ApiError(503,`unavailable`,`The service is temporarily unavailable.`);let{success:a}=await n.READ_LIMIT.limit({key:await rateLimitHash(e,i)});if(!a)throw new t.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:d})}async function writeLimit(e,n,r,i){let{success:a}=await n.WRITE_LIMIT.limit({key:await rateLimitHash(e,JSON.stringify([r,i]))});if(!a)throw new t.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:d})}async function cachedPublicGet(t,n,r,i){if(t.headers.get(`cookie`)!==null||t.headers.get(`authorization`)!==null)return r();let a=new URL(t.url);a.search=new URLSearchParams([...a.searchParams.entries()].sort(([e],[t])=>e<t?-1:+(e>t))).toString();let o=caches.default,s=new Request(a.toString(),{method:`GET`}),c=await o.match(s).catch(()=>{i()});if(c)return c;let l=await r();return l.headers.set(`cache-control`,`public, max-age=${e.ARCHIVE_LIMITS.publicCacheSeconds}, s-maxage=${e.ARCHIVE_LIMITS.publicCacheSeconds}`),n.waitUntil(o.put(s,l.clone()).catch(i)),l}var withCors=(e,n,r)=>{let i=t.corsHeaders(n,r);if(Object.keys(i).length===0)return e;let a=new Response(e.body,e);for(let[e,t]of Object.entries(i))a.headers.set(e,t);return a};function requireExpectedPublisher(n,r,i){if(i||r.publisher===null)return;let a=n.headers.get(e.PUBLISHER_HEADER);if(a===null||!c.test(a))throw new t.ApiError(400,`bad-request`,`The request is not valid.`);if(a!==r.publisher.id)throw new t.ApiError(409,`publisher-changed`,`The signed-in publisher changed since this request was prepared.`)}function createArchiveWorker(s){let u=new Map;for(let e of s){if(u.has(e.name))throw new n.ArchiveDefinitionError(`Duplicate archive collection: ${e.name}`);u.set(e.name,e)}let collectionFor=e=>{let n=u.get(e);if(n===void 0)throw new t.ApiError(404,`not-found`,`No such route.`);return n};async function handle(e,n,r){let s=a.apiConfig(n),d=new URL(e.url),{pathname:f}=d,p=e.method;if(!f.startsWith(`/api/v1/`))throw new t.ApiError(404,`not-found`,`No such route.`);if(p===`OPTIONS`)return t.preflight(e,s);let m=matchRoute(f);if(m===null)throw new t.ApiError(404,`not-found`,`No such route.`);if(!m.methods.includes(p))throw notAllowed();if(l.has(m.id)&&!c.test(m.segments[1]??``))throw t.badRequest();if(m.id===`health`)return withCors(await o.healthJson(s,n),e,s);m.id===`reports`&&t.queryParams(d,[`cursor`,`limit`,`status`]),await readLimit(s,n,e);let h=p!==`GET`&&p!==`HEAD`&&p!==`OPTIONS`;h&&t.requireMutationGuard(e,s);let g=p===`GET`&&(m.id===`meta`||m.id===`entries`||m.id===`entry`),_=m.id===`signOut`&&p===`POST`,v=g||_?null:await i.verifySession(e,s,n,m.id===`accountErase`);if(h){if(_)return withCors(t.json(200,{ok:!0},{"cache-control":`no-store`,"set-cookie":t.expiredSessionCookie(s.appEnv===`production`)}),e,s);let r=i.requireSession(v);requireExpectedPublisher(e,r,m.id===`me`&&p===`POST`),await writeLimit(s,n,r.subject.issuer,r.subject.subject)}let jsonWithCors=t=>withCors(t,e,s);switch(m.id){case`meta`:return t.queryParams(d,[]),jsonWithCors(o.collectionMeta(collectionFor(m.segments[0]??``)));case`entries`:{let t=collectionFor(m.segments[0]??``);if(p===`GET`)return jsonWithCors(await cachedPublicGet(e,r,()=>o.listEntries(t,e,n),()=>emit({diagnostic:{kind:`cache`},type:`cache-error`})));if(p===`POST`)return jsonWithCors(await o.publishEntry(t,e,n,s,i.requireSession(v)));throw notAllowed()}case`entry`:{let r=collectionFor(m.segments[0]??``),a=m.segments[1]??``;if(p===`GET`)return t.queryParams(d,[]),jsonWithCors(await o.getEntry(r,n,a));if(p===`PUT`)return jsonWithCors(await o.updateEntry(r,e,n,s,i.requireSession(v),a));if(p===`DELETE`)return jsonWithCors(await o.deleteEntry(r,n,i.requireSession(v),a));throw notAllowed()}case`like`:{let e=collectionFor(m.segments[0]??``),t=m.segments[1]??``;if(p===`PUT`)return jsonWithCors(await o.setLike(e,n,s,i.requireSession(v),t,!0));if(p===`DELETE`)return jsonWithCors(await o.setLike(e,n,s,i.requireSession(v),t,!1));throw notAllowed()}case`report`:{if(p!==`POST`)throw notAllowed();let t=collectionFor(m.segments[0]??``);return jsonWithCors(await o.reportEntry(t,e,n,i.requireSession(v),m.segments[1]??``))}case`hidden`:{if(p!==`PUT`&&p!==`DELETE`)throw notAllowed();let e=collectionFor(m.segments[0]??``);return jsonWithCors(await o.setHidden(e,n,i.requireSession(v),m.segments[1]??``,p===`PUT`))}case`reports`:{let t=i.requireSession(v);return requireExpectedPublisher(e,t,!1),jsonWithCors(await o.listReports(collectionFor(m.segments[0]??``),e,n,t))}case`resolveReport`:return t.queryParams(d,[]),jsonWithCors(await o.resolveReport(collectionFor(m.segments[0]??``),n,i.requireSession(v),m.segments[1]??``));case`modLog`:{let t=i.requireSession(v);return requireExpectedPublisher(e,t,!1),jsonWithCors(await o.listModerationLog(e,n,t))}case`publisherErase`:if(p!==`DELETE`)throw notAllowed();return jsonWithCors(await o.erasePublisher(n,i.requireSession(v),m.segments[0]??``));case`accountErase`:return t.queryParams(d,[]),jsonWithCors(await o.deleteAccountProfile(n,i.requireSession(v)));case`me`:if(p===`GET`)return jsonWithCors(o.getMe(v));if(p===`POST`)return jsonWithCors(await o.registerMe(e,n,i.requireSession(v)));if(p===`PUT`)return jsonWithCors(await o.renameMe(e,n,i.requireSession(v)));if(p===`DELETE`)return jsonWithCors(await o.deleteProfile(n,i.requireSession(v)));throw notAllowed();case`mePublications`:case`meLikes`:case`meReports`:{let r=i.requireSession(v);if(requireExpectedPublisher(e,r,!1),m.id===`mePublications`){let e=t.queryParams(d,[`collection`]).get(`collection`);if(e===null)throw t.badRequest();let i=u.get(e);if(i===void 0)throw t.badRequest();return jsonWithCors(await o.myPublications(i,n,r))}return jsonWithCors(await(m.id===`meReports`?o.myReports(e,n,r):o.myLikes(e,n,r)))}default:throw notAllowed()}}let d=new Set,emit=e=>{if(d.size!==0)for(let t of d)try{t(e)}catch{}};return{async fetch(r,i,o){let s=crypto.randomUUID(),c=routeLabel(new URL(r.url).pathname),l=Date.now();try{let t=await handle(r,i,o),n;return t.status===503&&c===`/health`&&(n={kind:e.archiveHealthSchema.parse(await t.clone().json()).reason===`database-unavailable`?`database`:`schema`}),emit({code:t.ok?`ok`:`unavailable`,ms:Date.now()-l,requestId:s,route:c,status:t.status,type:`request`,...n===void 0?{}:{diagnostic:n}}),t}catch(e){let o=t.toErrorResponse(e,s);emit({code:e instanceof t.ApiError?e.code:`internal`,diagnostic:e instanceof t.ApiError?e.diagnostic:{kind:e instanceof n.ArchiveDerivationError?`collection`:`internal`},ms:Date.now()-l,requestId:s,route:c,status:o.status,type:`request`});try{return withCors(o,r,a.apiConfig(i))}catch{return o}}},async scheduled(e,t,n){try{let e=Date.now(),n=await t.DB.batch([t.DB.prepare(r.SQL.expireRequests).bind(e),t.DB.prepare(r.SQL.expireRevocations).bind(e)]);emit({removed:n.reduce((e,t)=>e+(t.meta?.changes??0),0),status:`ok`,type:`scheduled`})}catch(e){throw emit({diagnostic:{kind:`database`},status:`failed`,type:`scheduled`}),e}},tap(e,t){let n=t?.signal;if(n?.aborted)return()=>{};let off=()=>{d.delete(e),n?.removeEventListener(`abort`,off)};return d.add(e),n?.addEventListener(`abort`,off,{once:!0}),off}}}exports.createArchiveWorker=createArchiveWorker;
|
|
1
|
+
const e=require("../contract.cjs"),t=require("../errors.cjs"),n=require("./auth.cjs"),r=require("./env.cjs"),i=require("./http.cjs"),a=require("./publications.cjs"),o=require("./sql.cjs");var s=[{id:`accountErase`,label:`/me/account`,methods:[`DELETE`],pattern:/^\/api\/v1\/me\/account$/},{id:`health`,label:`/health`,methods:[`GET`],pattern:/^\/api\/v1\/health$/},{id:`signOut`,label:`/sign-out`,methods:[`POST`],pattern:/^\/api\/v1\/sign-out$/},{id:`resolveReport`,label:`/collections/:collection/reports/:id/resolved`,methods:[`PUT`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports\/([0-9a-f-]{36})\/resolved$/i},{id:`meta`,label:`/collections/:collection`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)$/},{id:`reports`,label:`/collections/:collection/reports`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports$/},{id:`entries`,label:`/collections/:collection/entries`,methods:[`GET`,`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries$/},{id:`entry`,label:`/collections/:collection/entries/:id`,methods:[`GET`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})$/i},{id:`like`,label:`/collections/:collection/entries/:id/like`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/like$/i},{id:`report`,label:`/collections/:collection/entries/:id/report`,methods:[`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/report$/i},{id:`hidden`,label:`/collections/:collection/entries/:id/hidden`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/hidden$/i},{id:`modLog`,label:`/moderation-log`,methods:[`GET`],pattern:/^\/api\/v1\/moderation-log$/},{id:`publisherErase`,label:`/publishers/:id`,methods:[`DELETE`],pattern:/^\/api\/v1\/publishers\/([0-9a-f-]{36})$/i},{id:`me`,label:`/me`,methods:[`GET`,`POST`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/me$/},{id:`mePublications`,label:`/me/publications`,methods:[`GET`],pattern:/^\/api\/v1\/me\/publications$/},{id:`meLikes`,label:`/me/likes`,methods:[`GET`],pattern:/^\/api\/v1\/me\/likes$/},{id:`meReports`,label:`/me/reports`,methods:[`GET`],pattern:/^\/api\/v1\/me\/reports$/}],matchRoute=e=>{for(let t of s){let n=e.match(t.pattern);if(n)return{id:t.id,label:t.label,methods:t.methods,segments:n.slice(1)}}return null},routeLabel=e=>matchRoute(e)?.label??`unknown`,c=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,l=new Set([`entry`,`like`,`report`,`hidden`,`resolveReport`]),notAllowed=()=>new i.ApiError(405,`method-not-allowed`,`That method is not supported here.`),u=new WeakMap,rateLimitHash=async(e,t)=>{let n=u.get(e);n===void 0&&(n=crypto.subtle.importKey(`raw`,new TextEncoder().encode(e.rateLimitPepper),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),u.set(e,n));let r=await crypto.subtle.sign(`HMAC`,await n,new TextEncoder().encode(t));return[...new Uint8Array(r)].map(e=>e.toString(16).padStart(2,`0`)).join(``)},d=60;async function readLimit(e,t,n){let r=n.headers.get(`cf-connecting-ip`)??(e.appEnv===`local`?`127.0.0.1`:null);if(r===null)throw new i.ApiError(503,`unavailable`,`The service is temporarily unavailable.`);let{success:a}=await t.READ_LIMIT.limit({key:await rateLimitHash(e,r)});if(!a)throw new i.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:d})}async function writeLimit(e,t,n,r){let{success:a}=await t.WRITE_LIMIT.limit({key:await rateLimitHash(e,JSON.stringify([n,r]))});if(!a)throw new i.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:d})}async function cachedPublicGet(t,n,r,i){if(t.headers.get(`cookie`)!==null||t.headers.get(`authorization`)!==null)return r();let a=new URL(t.url);a.search=new URLSearchParams([...a.searchParams.entries()].sort(([e],[t])=>e<t?-1:+(e>t))).toString();let o=caches.default,s=new Request(a.toString(),{method:`GET`}),c=await o.match(s).catch(()=>{i()});if(c)return c;let l=await r();return l.headers.set(`cache-control`,`public, max-age=${e.ARCHIVE_LIMITS.publicCacheSeconds}, s-maxage=${e.ARCHIVE_LIMITS.publicCacheSeconds}`),n.waitUntil(o.put(s,l.clone()).catch(i)),l}var withCors=(e,t,n)=>{let r=i.corsHeaders(t,n);if(Object.keys(r).length===0)return e;let a=new Response(e.body,e);for(let[e,t]of Object.entries(r))a.headers.set(e,t);return a};function requireExpectedPublisher(t,n,r){if(r||n.publisher===null)return;let a=t.headers.get(e.PUBLISHER_HEADER);if(a===null||!c.test(a))throw new i.ApiError(400,`bad-request`,`The request is not valid.`);if(a!==n.publisher.id)throw new i.ApiError(409,`publisher-changed`,`The signed-in publisher changed since this request was prepared.`)}function createArchiveWorker(s){let u=new Map;for(let e of s){if(u.has(e.name))throw new t.ArchiveDefinitionError(`Duplicate archive collection: ${e.name}`);u.set(e.name,e)}let collectionFor=e=>{let t=u.get(e);if(t===void 0)throw new i.ApiError(404,`not-found`,`No such route.`);return t};async function handle(e,t,o){let s=r.apiConfig(t),d=new URL(e.url),{pathname:f}=d,p=e.method;if(!f.startsWith(`/api/v1/`))throw new i.ApiError(404,`not-found`,`No such route.`);if(p===`OPTIONS`)return i.preflight(e,s);let m=matchRoute(f);if(m===null)throw new i.ApiError(404,`not-found`,`No such route.`);if(!m.methods.includes(p))throw notAllowed();if(l.has(m.id)&&!c.test(m.segments[1]??``))throw i.badRequest();if(m.id===`health`)return withCors(await a.healthJson(s,t),e,s);m.id===`reports`&&i.queryParams(d,[`cursor`,`limit`,`status`]),await readLimit(s,t,e);let h=p!==`GET`&&p!==`HEAD`&&p!==`OPTIONS`;h&&i.requireMutationGuard(e,s);let g=p===`GET`&&(m.id===`meta`||m.id===`entries`||m.id===`entry`),_=m.id===`signOut`&&p===`POST`,v=g||_?null:await n.verifySession(e,s,t,m.id===`accountErase`);if(h){if(_)return withCors(i.json(200,{ok:!0},{"cache-control":`no-store`,"set-cookie":i.expiredSessionCookie(s.appEnv===`production`)}),e,s);let r=n.requireSession(v);requireExpectedPublisher(e,r,m.id===`me`&&p===`POST`),await writeLimit(s,t,r.subject.issuer,r.subject.subject)}let jsonWithCors=t=>withCors(t,e,s);switch(m.id){case`meta`:return i.queryParams(d,[]),jsonWithCors(a.collectionMeta(collectionFor(m.segments[0]??``)));case`entries`:{let r=collectionFor(m.segments[0]??``);if(p===`GET`)return jsonWithCors(await cachedPublicGet(e,o,()=>a.listEntries(r,e,t),()=>emit({diagnostic:{kind:`cache`},type:`cache-error`})));if(p===`POST`)return jsonWithCors(await a.publishEntry(r,e,t,s,n.requireSession(v)));throw notAllowed()}case`entry`:{let r=collectionFor(m.segments[0]??``),o=m.segments[1]??``;if(p===`GET`)return i.queryParams(d,[]),jsonWithCors(await a.getEntry(r,t,o));if(p===`PUT`)return jsonWithCors(await a.updateEntry(r,e,t,s,n.requireSession(v),o));if(p===`DELETE`)return jsonWithCors(await a.deleteEntry(r,t,n.requireSession(v),o));throw notAllowed()}case`like`:{let e=collectionFor(m.segments[0]??``),r=m.segments[1]??``;if(p===`PUT`)return jsonWithCors(await a.setLike(e,t,s,n.requireSession(v),r,!0));if(p===`DELETE`)return jsonWithCors(await a.setLike(e,t,s,n.requireSession(v),r,!1));throw notAllowed()}case`report`:{if(p!==`POST`)throw notAllowed();let r=collectionFor(m.segments[0]??``);return jsonWithCors(await a.reportEntry(r,e,t,n.requireSession(v),m.segments[1]??``))}case`hidden`:{if(p!==`PUT`&&p!==`DELETE`)throw notAllowed();let e=collectionFor(m.segments[0]??``);return jsonWithCors(await a.setHidden(e,t,n.requireSession(v),m.segments[1]??``,p===`PUT`))}case`reports`:{let r=n.requireSession(v);return requireExpectedPublisher(e,r,!1),jsonWithCors(await a.listReports(collectionFor(m.segments[0]??``),e,t,r))}case`resolveReport`:return i.queryParams(d,[]),jsonWithCors(await a.resolveReport(collectionFor(m.segments[0]??``),t,n.requireSession(v),m.segments[1]??``));case`modLog`:{let r=n.requireSession(v);return requireExpectedPublisher(e,r,!1),jsonWithCors(await a.listModerationLog(e,t,r))}case`publisherErase`:if(p!==`DELETE`)throw notAllowed();return jsonWithCors(await a.erasePublisher(t,n.requireSession(v),m.segments[0]??``));case`accountErase`:return i.queryParams(d,[]),jsonWithCors(await a.deleteAccountProfile(t,n.requireSession(v)));case`me`:if(p===`GET`)return jsonWithCors(a.getMe(v));if(p===`POST`)return jsonWithCors(await a.registerMe(e,t,n.requireSession(v)));if(p===`PUT`)return jsonWithCors(await a.renameMe(e,t,n.requireSession(v)));if(p===`DELETE`)return jsonWithCors(await a.deleteProfile(t,n.requireSession(v)));throw notAllowed();case`mePublications`:case`meLikes`:case`meReports`:{let r=n.requireSession(v);if(requireExpectedPublisher(e,r,!1),m.id===`mePublications`){let e=i.queryParams(d,[`collection`]).get(`collection`);if(e===null)throw i.badRequest();let n=u.get(e);if(n===void 0)throw i.badRequest();return jsonWithCors(await a.myPublications(n,t,r))}return jsonWithCors(await(m.id===`meReports`?a.myReports(e,t,r):a.myLikes(e,t,r)))}default:throw notAllowed()}}let d=new Set,emit=e=>{if(d.size!==0)for(let t of d)try{t(e)}catch{}};return{async fetch(n,a,o){let s=crypto.randomUUID(),c=routeLabel(new URL(n.url).pathname),l=Date.now();try{let t=await handle(n,a,o),r;return t.status===503&&c===`/health`&&(r={kind:e.archiveHealthSchema.parse(await t.clone().json()).reason===`database-unavailable`?`database`:`schema`}),emit({code:t.ok?`ok`:`unavailable`,ms:Date.now()-l,requestId:s,route:c,status:t.status,type:`request`,...r===void 0?{}:{diagnostic:r}}),t}catch(e){let o=i.toErrorResponse(e,s);emit({code:e instanceof i.ApiError?e.code:`internal`,diagnostic:e instanceof i.ApiError?e.diagnostic:{kind:e instanceof t.ArchiveDerivationError?`collection`:`internal`},ms:Date.now()-l,requestId:s,route:c,status:o.status,type:`request`});try{return withCors(o,n,r.apiConfig(a))}catch{return o}}},async scheduled(e,t,n){try{let e=Date.now(),n=await t.DB.batch([t.DB.prepare(o.SQL.expireRequests).bind(e),t.DB.prepare(o.SQL.expireRevocations).bind(e)]);emit({removed:n.reduce((e,t)=>e+(t.meta?.changes??0),0),status:`ok`,type:`scheduled`})}catch(e){throw emit({diagnostic:{kind:`database`},status:`failed`,type:`scheduled`}),e}},tap(e,t){let n=t?.signal;if(n?.aborted)return()=>{};let off=()=>{d.delete(e),n?.removeEventListener(`abort`,off)};return d.add(e),n?.addEventListener(`abort`,off,{once:!0}),off}}}exports.createArchiveWorker=createArchiveWorker;
|
|
2
2
|
//# sourceMappingURL=worker.cjs.map
|