feat: add custom widget flow workbench
This commit is contained in:
1 parent
c32a5317ca
commit
a6bdbbb4ea
259 files changed
+23875
-1293
No files matched your search
@@ -0,0 +1,44 @@
|
||||
/** Keep public schema metadata honest. Full runtime validation also runs in
|
||||
* Homarr's shared validator before publication, preview, and installation. */
|
||||
const validateSubmissionSchema = (record) => {
|
||||
if (record.getString("type") === "customCss") {
|
||||
if (record.getString("widgetSchema") !== "homarr-custom-css-v1") {
|
||||
throw new BadRequestError("CSS submission schema does not match its type");
|
||||
}
|
||||
return;
|
||||
}
|
||||
let widget;
|
||||
try {
|
||||
widget = JSON.parse(record.getString("content"));
|
||||
} catch {
|
||||
throw new BadRequestError("Widget content must be JSON");
|
||||
}
|
||||
if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
|
||||
throw new BadRequestError("Widget content must be an object");
|
||||
}
|
||||
if (widget.$schema !== "homarr-custom-widget-v2" && widget.$schema !== "homarr-custom-widget-v3") {
|
||||
throw new BadRequestError("Unsupported widget schema");
|
||||
}
|
||||
if (widget.$schema !== record.getString("widgetSchema")) {
|
||||
throw new BadRequestError("Widget schema metadata must match its content");
|
||||
}
|
||||
if (widget.$schema === "homarr-custom-widget-v2" && widget.extensions !== undefined) {
|
||||
throw new BadRequestError("Widget extensions require homarr-custom-widget-v3");
|
||||
}
|
||||
const allowedFields = [
|
||||
"$schema",
|
||||
"name",
|
||||
"description",
|
||||
"iconUrl",
|
||||
"sources",
|
||||
"requests",
|
||||
"options",
|
||||
"template",
|
||||
"extensions",
|
||||
];
|
||||
if (Object.keys(widget).some((key) => !allowedFields.includes(key))) {
|
||||
throw new BadRequestError("Widget content contains unsupported fields");
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { validateSubmissionSchema };
|
||||
@@ -58,6 +58,8 @@ routerAdd("GET", "/workshop/{id}", workshopDetailPage);
|
||||
routerAdd("GET", "/workshop/{id}/{$}", workshopDetailPage);
|
||||
|
||||
onRecordCreateRequest((event) => {
|
||||
const { validateSubmissionSchema } = require(`${__hooks}/workshop-content.js`);
|
||||
validateSubmissionSchema(event.record);
|
||||
event.record.set("revision", 1);
|
||||
event.record.set("expectedRevision", 0);
|
||||
event.record.set("changelog", "");
|
||||
@@ -68,6 +70,13 @@ onRecordCreateRequest((event) => {
|
||||
onRecordUpdateRequest((event) => {
|
||||
const { rejectRequest } = require(`${__hooks}/workshop-utils.js`);
|
||||
const original = event.record.original();
|
||||
if (
|
||||
original.getString("content") !== event.record.getString("content") ||
|
||||
original.getString("widgetSchema") !== event.record.getString("widgetSchema")
|
||||
) {
|
||||
const { validateSubmissionSchema } = require(`${__hooks}/workshop-content.js`);
|
||||
validateSubmissionSchema(event.record);
|
||||
}
|
||||
const currentRevision = original.getInt("revision");
|
||||
const expectedRevision = event.record.getInt("expectedRevision");
|
||||
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
|
||||
const identityUnchanged = "@request.body.author:changed = false && @request.body.type:changed = false";
|
||||
const schemaUnchanged = "@request.body.widgetSchema:changed = false";
|
||||
const supportedWidgetTransition =
|
||||
"type = 'customWidget' && " +
|
||||
"(widgetSchema = 'homarr-custom-widget-v2' || widgetSchema = 'homarr-custom-widget-v3') && " +
|
||||
"(@request.body.widgetSchema = 'homarr-custom-widget-v2' || @request.body.widgetSchema = 'homarr-custom-widget-v3')";
|
||||
const moderatorUnchanged = ["title", "description", "content", "changelog", "screenshots"]
|
||||
.map((field) => `@request.body.${field}:changed = false`)
|
||||
.join(" && ");
|
||||
const submissionRule = (authorSchemaRule) =>
|
||||
`(author = @request.auth.id && ${identityUnchanged} && (${authorSchemaRule})) || ` +
|
||||
`(@request.auth.isAdmin = true && author != @request.auth.id && ${identityUnchanged} && ${schemaUnchanged} && ${moderatorUnchanged})`;
|
||||
|
||||
migrate(
|
||||
(app) => {
|
||||
const submissions = app.findCollectionByNameOrId("submissions");
|
||||
submissions.updateRule = submissionRule(`${schemaUnchanged} || (${supportedWidgetTransition})`);
|
||||
app.save(submissions);
|
||||
},
|
||||
(app) => {
|
||||
const submissions = app.findCollectionByNameOrId("submissions");
|
||||
submissions.updateRule = submissionRule(schemaUnchanged);
|
||||
app.save(submissions);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user