fix(custom-widgets): address final v2 review feedback (#6451)
This commit is contained in:
1 parent
df967751f6
commit
4f6e335f72
20 files changed
+186
-92
No files matched your search
@@ -7,11 +7,6 @@ const rejectRequest = (message) => {
|
||||
throw new BadRequestError(message);
|
||||
};
|
||||
|
||||
const requestBodyValue = (event, name) => {
|
||||
const body = event.requestInfo().body;
|
||||
return body && typeof body.get === "function" ? body.get(name) : body ? body[name] : undefined;
|
||||
};
|
||||
|
||||
const validateWidgetManifest = (content) => {
|
||||
let widget;
|
||||
try {
|
||||
@@ -87,7 +82,7 @@ const sendEmail = (app, recipientEmail, subject, text, html) => {
|
||||
module.exports = {
|
||||
emailTemplate,
|
||||
escapeHtml,
|
||||
requestBodyValue,
|
||||
rejectRequest,
|
||||
sendEmail,
|
||||
validateAndNormalizeSubmission,
|
||||
};
|
||||
@@ -15,20 +15,22 @@ onRecordCreateRequest((event) => {
|
||||
const { validateAndNormalizeSubmission } = require(`${__hooks}/workshop-utils.js`);
|
||||
validateAndNormalizeSubmission(event.record);
|
||||
event.record.set("revision", 1);
|
||||
event.record.set("expectedRevision", 0);
|
||||
event.record.set("changelog", "");
|
||||
event.record.set("outdated", false);
|
||||
event.next();
|
||||
}, "submissions");
|
||||
|
||||
onRecordUpdateRequest((event) => {
|
||||
const { requestBodyValue, validateAndNormalizeSubmission } = require(`${__hooks}/workshop-utils.js`);
|
||||
const { rejectRequest, validateAndNormalizeSubmission } = require(`${__hooks}/workshop-utils.js`);
|
||||
const original = event.record.original();
|
||||
const currentRevision = original.getInt("revision");
|
||||
const expectedRevision = Number(requestBodyValue(event, "expectedRevision"));
|
||||
if (!Number.isSafeInteger(expectedRevision) || expectedRevision !== currentRevision) {
|
||||
const expectedRevision = event.record.getInt("expectedRevision");
|
||||
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
|
||||
rejectRequest("Submission changed since it was read");
|
||||
}
|
||||
validateAndNormalizeSubmission(event.record);
|
||||
event.record.set("expectedRevision", expectedRevision);
|
||||
event.record.set("revision", currentRevision + 1);
|
||||
event.next();
|
||||
}, "submissions");
|
||||
|
||||
@@ -88,6 +88,7 @@ migrate(
|
||||
{ type: "text", name: "widgetSchema", required: true, min: 1, max: 100 },
|
||||
{ type: "text", name: "content", required: true, max: 1000000 },
|
||||
{ type: "number", name: "revision", required: true, onlyInt: true, min: 1 },
|
||||
{ type: "number", name: "expectedRevision", onlyInt: true, min: 0 },
|
||||
{ type: "text", name: "changelog", max: 2000 },
|
||||
{ type: "bool", name: "outdated" },
|
||||
{
|
||||
@@ -112,9 +113,9 @@ migrate(
|
||||
`CREATE TRIGGER submissions_revision_cas
|
||||
BEFORE UPDATE ON submissions
|
||||
FOR EACH ROW
|
||||
WHEN NEW.revision != OLD.revision + 1
|
||||
WHEN NEW.expectedRevision != OLD.revision OR NEW.revision != OLD.revision + 1
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'submission revision must increment exactly once');
|
||||
SELECT RAISE(ABORT, 'submission revision compare-and-swap failed');
|
||||
END`,
|
||||
)
|
||||
.execute();
|
||||
@@ -285,8 +286,6 @@ migrate(
|
||||
if (field) users.fields.removeById(field.id);
|
||||
}
|
||||
app.save(users);
|
||||
} else {
|
||||
app.delete(users);
|
||||
}
|
||||
app.delete(stateCollection);
|
||||
},
|
||||
|
||||
@@ -9,7 +9,19 @@ if (typeof clientExport !== "string") throw new Error("Workshop client export is
|
||||
await access(resolve("packages/workshop", clientExport));
|
||||
|
||||
const hook = await read("apps/workshop/pb_hooks/workshop.pb.js");
|
||||
await access(resolve("apps/workshop/pb_hooks/workshop-utils.js"));
|
||||
const hookUtils = await read("apps/workshop/pb_hooks/workshop-utils.js");
|
||||
const schema = await read("packages/workshop/src/schema.ts");
|
||||
const constantValue = (source, name) => {
|
||||
const match = source.match(new RegExp(`(?:export )?const ${name} = ([\\d_]+)`));
|
||||
if (!match?.[1]) throw new Error(`Workshop limit ${name} is missing or is not a numeric literal`);
|
||||
return Number(match[1].replaceAll("_", ""));
|
||||
};
|
||||
if (constantValue(hookUtils, "MAX_CSS_LENGTH") !== constantValue(schema, "MAX_WORKSHOP_CSS_LENGTH")) {
|
||||
throw new Error("PocketBase and shared Workshop CSS limits must match");
|
||||
}
|
||||
if (constantValue(hookUtils, "MAX_CONTENT_LENGTH") !== constantValue(schema, "MAX_WORKSHOP_CONTENT_LENGTH")) {
|
||||
throw new Error("PocketBase and shared Workshop content limits must match");
|
||||
}
|
||||
if (!hook.includes("require(`${__hooks}/workshop-utils.js`)")) {
|
||||
throw new Error("Workshop handlers must load shared helpers inside their isolated PocketBase contexts");
|
||||
}
|
||||
@@ -25,6 +37,7 @@ for (const required of [
|
||||
"workshop_migration_state",
|
||||
"addedUserFields",
|
||||
"CREATE TRIGGER submissions_revision_cas",
|
||||
"NEW.expectedRevision != OLD.revision",
|
||||
"state.rateLimits.enabled",
|
||||
"users.passwordAuth = state.users.passwordAuth",
|
||||
"users.oauth2 = state.users.oauth2",
|
||||
|
||||
@@ -125,7 +125,13 @@ migration.up(app);
|
||||
if (!collections.has("workshop_migration_state") || settings.rateLimits.enabled !== true) {
|
||||
throw new Error("Workshop migration did not capture state before applying changes");
|
||||
}
|
||||
if (!rawQueries.some((query) => query.includes("CREATE TRIGGER submissions_revision_cas"))) {
|
||||
if (
|
||||
!rawQueries.some(
|
||||
(query) =>
|
||||
query.includes("CREATE TRIGGER submissions_revision_cas") &&
|
||||
query.includes("NEW.expectedRevision != OLD.revision"),
|
||||
)
|
||||
) {
|
||||
throw new Error("Workshop migration did not install the atomic submission revision guard");
|
||||
}
|
||||
migration.down(app);
|
||||
@@ -145,4 +151,23 @@ for (const name of ["workshop_migration_state", "workshop_listings", "reports",
|
||||
if (collections.has(name)) throw new Error(`Rollback left collection ${name}`);
|
||||
}
|
||||
|
||||
collections.clear();
|
||||
records.clear();
|
||||
rawQueries.length = 0;
|
||||
settings.rateLimits = structuredClone(original.rateLimits);
|
||||
|
||||
migration.up(app);
|
||||
const bootstrappedUsers = collections.get("users");
|
||||
if (!bootstrappedUsers) throw new Error("Fresh migration did not create the users collection");
|
||||
migration.down(app);
|
||||
if (collections.get("users") !== bootstrappedUsers) {
|
||||
throw new Error("Fresh rollback removed the users collection required by the bootstrap hook");
|
||||
}
|
||||
for (const name of ["workshop_migration_state", "workshop_listings", "reports", "comments", "votes", "submissions"]) {
|
||||
if (collections.has(name)) throw new Error(`Fresh rollback left collection ${name}`);
|
||||
}
|
||||
if (JSON.stringify(settings.rateLimits) !== JSON.stringify(original.rateLimits)) {
|
||||
throw new Error("Fresh rollback did not restore rate limits");
|
||||
}
|
||||
|
||||
console.log("Workshop migration up/down restoration passed");
|
||||
Reference in new issue
Block a user