fix(custom-widgets): harden v2 authoring

This commit is contained in:
Thomas Camlong committed 2026-07-20 22:16:18 +02:00
1 parent ee832ad3c6
commit 7c66c912e7
171 files changed
+20311 -50527

No files matched your search

@@ -12,43 +12,27 @@ migrate(
if (!users.fields.getByName("displayName"))
users.fields.add(new TextField({ name: "displayName", required: true, min: 1, max: 100 }));
if (!users.fields.getByName("avatarUrl")) users.fields.add(new URLField({ name: "avatarUrl" }));
app.save(users);
if (!users.fields.getByName("isAdmin")) users.fields.add(new BoolField({ name: "isAdmin" }));
const admins = new Collection({
type: "base",
name: "workshop_admins",
listRule: "user = @request.auth.id",
viewRule: "user = @request.auth.id",
createRule: null,
updateRule: null,
deleteRule: null,
fields: [
{ type: "relation", name: "user", required: true, maxSelect: 1, collectionId: users.id, cascadeDelete: true },
{ type: "autodate", name: "created", onCreate: true },
],
});
admins.addIndex("idx_workshop_admin_user", true, "user", "");
app.save(admins);
const adminRule = "@collection.workshop_admins.user ?= @request.auth.id";
const adminRule = "@request.auth.isAdmin = true";
users.listRule = adminRule;
users.viewRule = `id = @request.auth.id || ${adminRule}`;
users.updateRule = "id = @request.auth.id";
users.updateRule = "id = @request.auth.id && @request.body.isAdmin:isset = false";
users.deleteRule = null;
app.save(users);
const submissions = new Collection({
type: "base",
name: "submissions",
listRule: adminRule,
listRule: "",
viewRule: "",
createRule: "@request.auth.id != '' && @request.body.author = @request.auth.id",
updateRule: "author = @request.auth.id",
updateRule: "author = @request.auth.id && @request.body.author:changed = false",
deleteRule: `author = @request.auth.id || ${adminRule}`,
fields: [
{ type: "text", name: "title", required: true, min: 3, max: 100 },
{ type: "text", name: "description", max: 2000 },
{ type: "text", name: "content", required: true, max: 1000000 },
{ type: "text", name: "contentHash", required: true, max: 64 },
{
type: "file",
name: "screenshots",
@@ -58,9 +42,6 @@ migrate(
thumbs: ["480x320", "960x640"],
},
{ type: "relation", name: "author", required: true, maxSelect: 1, collectionId: users.id, cascadeDelete: true },
{ type: "text", name: "authorName", required: true, max: 100 },
{ type: "number", name: "revision", required: true, onlyInt: true, min: 1 },
{ type: "text", name: "changelog", max: 2000 },
{ type: "autodate", name: "created", onCreate: true },
{ type: "autodate", name: "updated", onCreate: true, onUpdate: true },
],
@@ -74,8 +55,10 @@ migrate(
name: "votes",
listRule: "user = @request.auth.id",
viewRule: "user = @request.auth.id",
createRule: "@request.auth.id != '' && @request.body.user = @request.auth.id",
updateRule: "user = @request.auth.id",
createRule:
"@request.auth.id != '' && @request.body.user = @request.auth.id && (@request.body.value = 1 || @request.body.value = -1)",
updateRule:
"user = @request.auth.id && @request.body.user:changed = false && @request.body.submission:changed = false && (@request.body.value = 1 || @request.body.value = -1)",
deleteRule: "user = @request.auth.id",
fields: [
{
@@ -102,8 +85,8 @@ migrate(
listRule: adminRule,
viewRule: `reporter = @request.auth.id || ${adminRule}`,
createRule: "@request.auth.id != '' && @request.body.reporter = @request.auth.id",
updateRule: adminRule,
deleteRule: null,
updateRule: null,
deleteRule: adminRule,
fields: [
{
type: "relation",
@@ -129,55 +112,29 @@ migrate(
values: ["malicious", "spam", "copyright", "inappropriate", "other"],
},
{ type: "text", name: "explanation", required: true, min: 3, max: 1000 },
{ type: "select", name: "status", required: true, maxSelect: 1, values: ["open", "dismissed"] },
{ type: "text", name: "dismissalReason", max: 1000 },
{ type: "autodate", name: "created", onCreate: true },
{ type: "autodate", name: "updated", onCreate: true, onUpdate: true },
],
});
reports.addIndex("idx_reports_reporter_submission", true, "reporter, submission", "");
reports.addIndex("idx_reports_status_created", false, "status, created", "");
reports.addIndex("idx_reports_created", false, "created", "");
app.save(reports);
const actions = new Collection({
type: "base",
name: "workshop_admin_actions",
listRule: adminRule,
viewRule: adminRule,
createRule: null,
updateRule: null,
deleteRule: null,
fields: [
{ type: "relation", name: "actor", required: true, maxSelect: 1, collectionId: users.id },
{
type: "select",
name: "action",
required: true,
maxSelect: 1,
values: ["delete_submission", "dismiss_report"],
},
{ type: "text", name: "submissionId", max: 30 },
{ type: "text", name: "reportId", max: 30 },
{ type: "text", name: "reason", max: 1000 },
{ type: "text", name: "snapshot", max: 100000 },
{ type: "autodate", name: "created", onCreate: true },
],
});
actions.addIndex("idx_workshop_admin_actions_created", false, "created", "");
app.save(actions);
const listings = new Collection({
type: "view",
name: "workshop_listings",
listRule: "",
viewRule: "",
viewQuery: `
SELECT s.id, s.title, s.description, s.contentHash, s.screenshots,
s.revision, s.changelog, s.author, s.authorName, s.created, s.updated,
SELECT s.id, s.title, s.description, s.screenshots, s.author, u.displayName AS authorName,
s.created, s.updated,
COALESCE(SUM(CASE WHEN v.value = 1 THEN 1 ELSE 0 END), 0) AS upvotes,
COALESCE(SUM(CASE WHEN v.value = -1 THEN 1 ELSE 0 END), 0) AS downvotes,
COALESCE(SUM(v.value), 0) AS score
FROM submissions s LEFT JOIN votes v ON v.submission = s.id GROUP BY s.id
FROM submissions s
JOIN users u ON u.id = s.author
LEFT JOIN votes v ON v.submission = s.id
GROUP BY s.id
`,
});
app.save(listings);
@@ -195,14 +152,7 @@ migrate(
app.save(settings);
},
(app) => {
for (const name of [
"workshop_listings",
"workshop_admin_actions",
"reports",
"votes",
"submissions",
"workshop_admins",
]) {
for (const name of ["workshop_listings", "reports", "votes", "submissions"]) {
try {
app.delete(app.findCollectionByNameOrId(name));
} catch {}