@musnows/scriverse 0.3.8 → 0.3.10
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/app.js +270 -85
- package/dist/app.js.map +1 -1
- package/dist/cli-contract.js +4 -4
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli-core.js +37 -6
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +41 -0
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +455 -122
- package/dist/public/index.html +40 -4
- package/dist/public/styles.css +55 -12
- package/dist/public/work-permissions.d.ts +14 -0
- package/dist/public/work-permissions.js +71 -0
- package/dist/store.js +432 -112
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +214 -32
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/work-permissions.js +128 -0
- package/dist/work-permissions.js.map +1 -0
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -26,6 +26,7 @@ import { createRequestLoggingMiddleware, sanitizeRequestPath } from "./http-logg
|
|
|
26
26
|
import { accountReference, logger, sanitizeError } from "./logger.js";
|
|
27
27
|
import { runWithRequestActor } from "./request-context.js";
|
|
28
28
|
import { APP_VERSION } from "./version.js";
|
|
29
|
+
import { fullWorkModulePermissions } from "./work-permissions.js";
|
|
29
30
|
import { clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, setSessionCookie, UserAuthService } from "./user-auth.js";
|
|
30
31
|
const nonEmpty = z.string().trim().min(1);
|
|
31
32
|
const identifier = z.string().trim().min(1).max(200);
|
|
@@ -55,11 +56,33 @@ const loginSchema = z.object({
|
|
|
55
56
|
...captchaFields
|
|
56
57
|
}).strict();
|
|
57
58
|
const userUpdateSchema = z.object({ role: z.enum(["admin", "user"]).optional(), status: z.enum(["active", "disabled"]).optional() }).strict();
|
|
58
|
-
const
|
|
59
|
-
const
|
|
59
|
+
const memberRoleValueSchema = z.enum(["editor", "settings-editor", "viewer"]);
|
|
60
|
+
const moduleAccessSchema = z.enum(["none", "read", "write"]);
|
|
61
|
+
const modulePermissionsSchema = z.object({
|
|
62
|
+
prose: moduleAccessSchema,
|
|
63
|
+
settings: moduleAccessSchema,
|
|
64
|
+
characters: moduleAccessSchema,
|
|
65
|
+
races: moduleAccessSchema,
|
|
66
|
+
organizations: moduleAccessSchema,
|
|
67
|
+
timeline: moduleAccessSchema,
|
|
68
|
+
relationships: moduleAccessSchema,
|
|
69
|
+
outlines: moduleAccessSchema,
|
|
70
|
+
reviews: moduleAccessSchema,
|
|
71
|
+
ai: moduleAccessSchema,
|
|
72
|
+
"ai-settings": moduleAccessSchema
|
|
73
|
+
}).strict();
|
|
74
|
+
const memberSchema = z.union([
|
|
75
|
+
z.object({ userId: identifier, permissions: modulePermissionsSchema }).strict(),
|
|
76
|
+
z.object({ userId: identifier, role: memberRoleValueSchema }).strict()
|
|
77
|
+
]);
|
|
78
|
+
const memberPermissionSchema = z.union([
|
|
79
|
+
z.object({ permissions: modulePermissionsSchema }).strict(),
|
|
80
|
+
z.object({ role: memberRoleValueSchema }).strict()
|
|
81
|
+
]);
|
|
60
82
|
const profileSchema = z.object({ displayName: z.string().trim().min(1).max(80) }).strict();
|
|
61
83
|
const passwordChangeSchema = z.object({ currentPassword: z.string().max(200), newPassword: passwordSchema }).strict();
|
|
62
84
|
const changeNoteSchema = z.string().trim().max(500).optional();
|
|
85
|
+
const expectedVersionNoSchema = z.coerce.number().int().positive().optional();
|
|
63
86
|
function validateImportedText(text) {
|
|
64
87
|
if (text.length > maximumImportedTextLength)
|
|
65
88
|
throw new AppError(413, "IMPORT_TEXT_TOO_LARGE", "导入文件解压后的文本超过 2000 万字符限制");
|
|
@@ -174,7 +197,7 @@ const organizationSchema = z.object({
|
|
|
174
197
|
description: z.string().max(100_000).optional(),
|
|
175
198
|
settings: z.array(z.string().trim().min(1).max(20_000)).max(200).optional(),
|
|
176
199
|
memberIds: z.array(identifier).max(1000).optional()
|
|
177
|
-
});
|
|
200
|
+
}).strict();
|
|
178
201
|
const raceSchema = z.object({
|
|
179
202
|
name: nonEmpty.max(200),
|
|
180
203
|
parentRaceId: identifier.nullable().optional(),
|
|
@@ -302,6 +325,55 @@ function noContent(response) {
|
|
|
302
325
|
function parse(schema, value) {
|
|
303
326
|
return schema.parse(value);
|
|
304
327
|
}
|
|
328
|
+
function recordValue(value) {
|
|
329
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
330
|
+
}
|
|
331
|
+
function mapRecords(value, mapper) {
|
|
332
|
+
if (Array.isArray(value))
|
|
333
|
+
return value.map((item) => recordValue(item) ? mapper(item) : item);
|
|
334
|
+
const record = recordValue(value);
|
|
335
|
+
if (!record)
|
|
336
|
+
return value;
|
|
337
|
+
if (Array.isArray(record.items)) {
|
|
338
|
+
return { ...record, items: record.items.map((item) => recordValue(item) ? mapper(item) : item) };
|
|
339
|
+
}
|
|
340
|
+
return mapper(record);
|
|
341
|
+
}
|
|
342
|
+
function redactCharacterLinks(record, permissions) {
|
|
343
|
+
const result = { ...record };
|
|
344
|
+
if (permissions.races === "none") {
|
|
345
|
+
result.raceId = null;
|
|
346
|
+
result.race = null;
|
|
347
|
+
result.species = "";
|
|
348
|
+
}
|
|
349
|
+
if (permissions.organizations === "none") {
|
|
350
|
+
result.organizationIds = [];
|
|
351
|
+
result.organizations = [];
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
function redactRaceMembers(record, permissions) {
|
|
356
|
+
return permissions.characters === "none" ? { ...record, memberIds: [], members: [] } : record;
|
|
357
|
+
}
|
|
358
|
+
function redactOrganizationMembers(record, permissions) {
|
|
359
|
+
return permissions.characters === "none" ? { ...record, memberIds: [], members: [] } : record;
|
|
360
|
+
}
|
|
361
|
+
function redactMergeRecords(value, mapper) {
|
|
362
|
+
const record = recordValue(value);
|
|
363
|
+
if (!record)
|
|
364
|
+
return value;
|
|
365
|
+
return {
|
|
366
|
+
...record,
|
|
367
|
+
...(recordValue(record.target) ? { target: mapper(record.target) } : {}),
|
|
368
|
+
...(recordValue(record.source) ? { source: mapper(record.source) } : {})
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function redactVersionSnapshots(value, mapper) {
|
|
372
|
+
return mapRecords(value, (version) => {
|
|
373
|
+
const snapshot = recordValue(version.snapshot);
|
|
374
|
+
return snapshot ? { ...version, snapshot: mapper(snapshot) } : version;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
305
377
|
export function createRuntime(options) {
|
|
306
378
|
logger.info("runtime.initializing", {
|
|
307
379
|
databasePath: options.databasePath,
|
|
@@ -318,6 +390,14 @@ export function createRuntime(options) {
|
|
|
318
390
|
mkdirSync(attachmentStorage.temporaryDirectory, { recursive: true, mode: 0o700 });
|
|
319
391
|
const auth = new UserAuthService(database);
|
|
320
392
|
const store = new Store(database);
|
|
393
|
+
const requestPermissions = (request, workId) => {
|
|
394
|
+
if (!request.authUser)
|
|
395
|
+
return fullWorkModulePermissions();
|
|
396
|
+
const resolvedWorkId = workId ?? auth.resolveWorkId(request.path) ?? undefined;
|
|
397
|
+
if (!resolvedWorkId)
|
|
398
|
+
return fullWorkModulePermissions();
|
|
399
|
+
return auth.workModulePermissions(request.authUser, resolvedWorkId, request.authMethod !== "api-key") ?? fullWorkModulePermissions();
|
|
400
|
+
};
|
|
321
401
|
const captcha = new ImageCaptchaService({ revealAnswer: options.revealCaptchaAnswer === true });
|
|
322
402
|
const ai = new AiManager(store, new CredentialVault(options.masterSecret), options.fetchImpl ?? fetch, options.security ? (url) => assertSafeAiEndpoint(url, options.security?.allowPrivateAiEndpoints) : undefined);
|
|
323
403
|
const app = express();
|
|
@@ -535,24 +615,43 @@ export function createRuntime(options) {
|
|
|
535
615
|
if (!request.authUser)
|
|
536
616
|
throw new AppError(401, "AUTH_REQUIRED", "请先登录");
|
|
537
617
|
const input = parse(memberSchema, request.body);
|
|
538
|
-
const
|
|
539
|
-
|
|
618
|
+
const permissionInput = "permissions" in input
|
|
619
|
+
? { permissions: input.permissions }
|
|
620
|
+
: { role: input.role };
|
|
621
|
+
const members = database.transaction(() => {
|
|
622
|
+
const result = auth.addMember(request.params.workId, input.userId, permissionInput, request.authUser.userId);
|
|
623
|
+
store.audit(request.params.workId, "work.member-added", "user", input.userId, permissionInput);
|
|
624
|
+
return result;
|
|
625
|
+
});
|
|
540
626
|
data(response, members, 201);
|
|
541
627
|
});
|
|
542
628
|
app.patch("/api/works/:workId/members/:userId", (request, response) => {
|
|
543
|
-
const input = parse(
|
|
544
|
-
const
|
|
545
|
-
|
|
629
|
+
const input = parse(memberPermissionSchema, request.body);
|
|
630
|
+
const permissionInput = "permissions" in input
|
|
631
|
+
? { permissions: input.permissions }
|
|
632
|
+
: { role: input.role };
|
|
633
|
+
const members = database.transaction(() => {
|
|
634
|
+
const result = auth.updateMemberPermissions(request.params.workId, request.params.userId, permissionInput);
|
|
635
|
+
store.audit(request.params.workId, "work.member-role-updated", "user", request.params.userId, permissionInput);
|
|
636
|
+
return result;
|
|
637
|
+
});
|
|
546
638
|
data(response, members);
|
|
547
639
|
});
|
|
548
640
|
app.delete("/api/works/:workId/members/:userId", (request, response) => {
|
|
549
|
-
const members =
|
|
550
|
-
|
|
641
|
+
const members = database.transaction(() => {
|
|
642
|
+
const result = auth.removeMember(request.params.workId, request.params.userId);
|
|
643
|
+
store.audit(request.params.workId, "work.member-removed", "user", request.params.userId);
|
|
644
|
+
return result;
|
|
645
|
+
});
|
|
551
646
|
data(response, members);
|
|
552
647
|
});
|
|
553
|
-
app.patch("/api/works/:workId", (request, response) =>
|
|
648
|
+
app.patch("/api/works/:workId", (request, response) => {
|
|
649
|
+
const { expectedVersionNo, changeNote, ...input } = parse(workSchema.partial().extend({ expectedVersionNo: expectedVersionNoSchema, changeNote: changeNoteSchema }).strict(), request.body);
|
|
650
|
+
data(response, store.updateWork(request.params.workId, input, expectedVersionNo, "manual", null, changeNote));
|
|
651
|
+
});
|
|
554
652
|
app.delete("/api/works/:workId", async (request, response) => {
|
|
555
|
-
const
|
|
653
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
654
|
+
const removableStorageKeys = store.deleteWork(request.params.workId, input.expectedVersionNo);
|
|
556
655
|
await Promise.all(removableStorageKeys.map((storageKey) => attachmentStorage.remove(storageKey)));
|
|
557
656
|
noContent(response);
|
|
558
657
|
});
|
|
@@ -574,10 +673,12 @@ export function createRuntime(options) {
|
|
|
574
673
|
const mimeType = isPng ? "image/png" : isJpeg ? "image/jpeg" : isWebp ? "image/webp" : null;
|
|
575
674
|
if (!mimeType)
|
|
576
675
|
throw new AppError(415, "INVALID_COVER", "封面文件内容不是有效的 PNG、JPEG 或 WebP 图片");
|
|
577
|
-
|
|
676
|
+
const expectedVersionNo = parse(expectedVersionNoSchema, request.body.expectedVersionNo);
|
|
677
|
+
data(response, store.setWorkCover(String(request.params.workId), mimeType, bytes, expectedVersionNo));
|
|
578
678
|
});
|
|
579
679
|
app.delete("/api/works/:workId/cover", (request, response) => {
|
|
580
|
-
|
|
680
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
681
|
+
store.deleteWorkCover(request.params.workId, input.expectedVersionNo);
|
|
581
682
|
noContent(response);
|
|
582
683
|
});
|
|
583
684
|
app.get("/api/works/:workId/file-versions", (request, response) => {
|
|
@@ -585,7 +686,8 @@ export function createRuntime(options) {
|
|
|
585
686
|
data(response, pagination ? store.listFileVersionsPage(request.params.workId, pagination) : store.listFileVersions(request.params.workId));
|
|
586
687
|
});
|
|
587
688
|
app.post("/api/works/:workId/file-versions/:fileVersionId/restore", (request, response) => {
|
|
588
|
-
|
|
689
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
690
|
+
data(response, store.restoreFileVersion(request.params.workId, request.params.fileVersionId, input.expectedVersionNo));
|
|
589
691
|
});
|
|
590
692
|
app.post("/api/works/:workId/import", upload.single("file"), async (request, response) => {
|
|
591
693
|
if (!request.file)
|
|
@@ -599,19 +701,23 @@ export function createRuntime(options) {
|
|
|
599
701
|
? await extractDocxText(request.file.buffer)
|
|
600
702
|
: decodeUtf8ImportedText(request.file.buffer));
|
|
601
703
|
const parsed = applyImportFileHints(parseNovelText(text), originalFileName);
|
|
602
|
-
|
|
704
|
+
const mode = parse(z.enum(["append", "overwrite"]), request.body.mode ?? "overwrite");
|
|
705
|
+
const expectedVersionNo = parse(expectedVersionNoSchema, request.body.expectedVersionNo);
|
|
706
|
+
data(response, store.importNovel(String(request.params.workId), originalFileName, extension.slice(1), parsed, mode, expectedVersionNo), 201);
|
|
603
707
|
});
|
|
604
708
|
app.post("/api/works/:workId/volumes", (request, response) => {
|
|
605
709
|
const input = parse(z.object({ title: nonEmpty.max(200), kind: z.enum(["main", "prequel", "extra", "epilogue", "appendix"]).optional(), description: z.string().max(5_000).optional(), keywords: z.array(nonEmpty.max(100)).max(100).optional() }), request.body);
|
|
606
710
|
data(response, store.createVolume(request.params.workId, input), 201);
|
|
607
711
|
});
|
|
608
712
|
app.patch("/api/volumes/:volumeId", (request, response) => {
|
|
609
|
-
const input = parse(z.object({ title: nonEmpty.max(200).optional(), kind: z.enum(["main", "prequel", "extra", "epilogue", "appendix"]).optional(), description: z.string().max(5_000).optional(), keywords: z.array(nonEmpty.max(100)).max(100).optional(), sortOrder: z.number().int().min(0).optional() }), request.body);
|
|
610
|
-
|
|
713
|
+
const input = parse(z.object({ title: nonEmpty.max(200).optional(), kind: z.enum(["main", "prequel", "extra", "epilogue", "appendix"]).optional(), description: z.string().max(5_000).optional(), keywords: z.array(nonEmpty.max(100)).max(100).optional(), sortOrder: z.number().int().min(0).optional(), expectedVersionNo: expectedVersionNoSchema, changeNote: changeNoteSchema }).strict(), request.body);
|
|
714
|
+
const { expectedVersionNo, changeNote, ...volumeInput } = input;
|
|
715
|
+
data(response, store.updateVolume(request.params.volumeId, volumeInput, expectedVersionNo, "manual", null, changeNote));
|
|
611
716
|
});
|
|
612
717
|
app.get("/api/volumes/:volumeId", (request, response) => data(response, store.getVolume(request.params.volumeId)));
|
|
613
718
|
app.delete("/api/volumes/:volumeId", (request, response) => {
|
|
614
|
-
|
|
719
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
720
|
+
store.deleteVolume(request.params.volumeId, input.expectedVersionNo);
|
|
615
721
|
noContent(response);
|
|
616
722
|
});
|
|
617
723
|
app.post("/api/works/:workId/chapters", (request, response) => {
|
|
@@ -620,12 +726,13 @@ export function createRuntime(options) {
|
|
|
620
726
|
});
|
|
621
727
|
app.get("/api/chapters/:chapterId", (request, response) => data(response, store.getChapter(request.params.chapterId)));
|
|
622
728
|
app.patch("/api/chapters/:chapterId", (request, response) => {
|
|
623
|
-
const input = parse(z.object({ title: nonEmpty.max(300).optional(), content: z.string().max(2_000_000).optional(), excludedFromAnalysis: z.boolean().optional(), chapterType: chapterTypeSchema.optional(), source: z.enum(["manual", "auto"]).optional(), changeNote: changeNoteSchema }).strict(), request.body);
|
|
624
|
-
const { source, changeNote, ...chapterInput } = input;
|
|
625
|
-
data(response, store.saveChapter(request.params.chapterId, chapterInput, source ?? "manual", null, changeNote));
|
|
729
|
+
const input = parse(z.object({ title: nonEmpty.max(300).optional(), content: z.string().max(2_000_000).optional(), excludedFromAnalysis: z.boolean().optional(), chapterType: chapterTypeSchema.optional(), source: z.enum(["manual", "auto"]).optional(), changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
730
|
+
const { source, changeNote, expectedVersionNo, ...chapterInput } = input;
|
|
731
|
+
data(response, store.saveChapter(request.params.chapterId, chapterInput, source ?? "manual", null, changeNote, expectedVersionNo));
|
|
626
732
|
});
|
|
627
733
|
app.delete("/api/chapters/:chapterId", (request, response) => {
|
|
628
|
-
|
|
734
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
735
|
+
store.deleteChapter(request.params.chapterId, input.expectedVersionNo);
|
|
629
736
|
noContent(response);
|
|
630
737
|
});
|
|
631
738
|
app.get("/api/chapters/:chapterId/versions", (request, response) => {
|
|
@@ -637,12 +744,13 @@ export function createRuntime(options) {
|
|
|
637
744
|
data(response, pagination ? store.listChapterInsightsPage(request.params.chapterId, pagination) : store.listChapterInsights(request.params.chapterId));
|
|
638
745
|
});
|
|
639
746
|
app.post("/api/chapters/:chapterId/restore", (request, response) => {
|
|
640
|
-
const input = parse(z.object({ versionNo: z.number().int().positive() }), request.body);
|
|
641
|
-
data(response, store.restoreChapter(request.params.chapterId, input.versionNo));
|
|
747
|
+
const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
748
|
+
data(response, store.restoreChapter(request.params.chapterId, input.versionNo, input.expectedVersionNo));
|
|
642
749
|
});
|
|
643
750
|
app.post("/api/chapters/:chapterId/move", (request, response) => {
|
|
644
|
-
const input = parse(z.object({ volumeId: identifier, sortOrder: z.number().int().min(0) }), request.body);
|
|
645
|
-
|
|
751
|
+
const input = parse(z.object({ volumeId: identifier, sortOrder: z.number().int().min(0), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
752
|
+
const { expectedVersionNo, ...moveInput } = input;
|
|
753
|
+
data(response, store.moveChapter(request.params.chapterId, moveInput, expectedVersionNo));
|
|
646
754
|
});
|
|
647
755
|
app.get("/api/works/:workId/outlines", (request, response) => {
|
|
648
756
|
const pagination = parsePagination(request.query);
|
|
@@ -650,11 +758,12 @@ export function createRuntime(options) {
|
|
|
650
758
|
});
|
|
651
759
|
app.get("/api/chapters/:chapterId/outline", (request, response) => data(response, store.getChapterOutline(request.params.chapterId)));
|
|
652
760
|
app.put("/api/chapters/:chapterId/outline", (request, response) => {
|
|
653
|
-
const { changeNote, ...input } = parse(chapterOutlineSchema.extend({ changeNote: changeNoteSchema }), request.body);
|
|
654
|
-
data(response, store.upsertChapterOutline(request.params.chapterId, input, "manual", null, changeNote));
|
|
761
|
+
const { changeNote, expectedVersionNo, ...input } = parse(chapterOutlineSchema.extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
762
|
+
data(response, store.upsertChapterOutline(request.params.chapterId, input, "manual", null, changeNote, expectedVersionNo));
|
|
655
763
|
});
|
|
656
764
|
app.delete("/api/chapters/:chapterId/outline", (request, response) => {
|
|
657
|
-
|
|
765
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
766
|
+
store.deleteChapterOutline(request.params.chapterId, input.expectedVersionNo);
|
|
658
767
|
noContent(response);
|
|
659
768
|
});
|
|
660
769
|
app.get("/api/works/:workId/foreshadows", (request, response) => {
|
|
@@ -672,21 +781,27 @@ export function createRuntime(options) {
|
|
|
672
781
|
});
|
|
673
782
|
app.get("/api/foreshadows/:foreshadowId", (request, response) => data(response, store.getForeshadow(request.params.foreshadowId)));
|
|
674
783
|
app.patch("/api/foreshadows/:foreshadowId", (request, response) => {
|
|
675
|
-
const { changeNote, ...input } = parse(foreshadowSchema.partial().extend({ changeNote: changeNoteSchema }), request.body);
|
|
676
|
-
data(response, store.updateForeshadow(request.params.foreshadowId, input, "manual", null, changeNote));
|
|
784
|
+
const { changeNote, expectedVersionNo, ...input } = parse(foreshadowSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
785
|
+
data(response, store.updateForeshadow(request.params.foreshadowId, input, "manual", null, changeNote, expectedVersionNo));
|
|
677
786
|
});
|
|
678
787
|
app.delete("/api/foreshadows/:foreshadowId", (request, response) => {
|
|
679
|
-
|
|
788
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
789
|
+
store.deleteForeshadow(request.params.foreshadowId, input.expectedVersionNo);
|
|
680
790
|
noContent(response);
|
|
681
791
|
});
|
|
682
792
|
app.post("/api/foreshadows/:foreshadowId/occurrences", (request, response) => {
|
|
683
|
-
|
|
793
|
+
const input = parse(foreshadowOccurrenceSchema.extend({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
794
|
+
const { expectedVersionNo, ...occurrenceInput } = input;
|
|
795
|
+
data(response, store.createForeshadowOccurrence(request.params.foreshadowId, occurrenceInput, expectedVersionNo), 201);
|
|
684
796
|
});
|
|
685
797
|
app.patch("/api/foreshadow-occurrences/:occurrenceId", (request, response) => {
|
|
686
|
-
|
|
798
|
+
const input = parse(foreshadowOccurrenceSchema.partial().extend({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
799
|
+
const { expectedVersionNo, ...occurrenceInput } = input;
|
|
800
|
+
data(response, store.updateForeshadowOccurrence(request.params.occurrenceId, occurrenceInput, expectedVersionNo));
|
|
687
801
|
});
|
|
688
802
|
app.delete("/api/foreshadow-occurrences/:occurrenceId", (request, response) => {
|
|
689
|
-
|
|
803
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
804
|
+
store.deleteForeshadowOccurrence(request.params.occurrenceId, input.expectedVersionNo);
|
|
690
805
|
noContent(response);
|
|
691
806
|
});
|
|
692
807
|
app.get("/api/works/:workId/settings", (request, response) => {
|
|
@@ -696,11 +811,12 @@ export function createRuntime(options) {
|
|
|
696
811
|
app.post("/api/works/:workId/settings", (request, response) => data(response, store.createSetting(request.params.workId, parse(settingSchema, request.body)), 201));
|
|
697
812
|
app.get("/api/settings/:settingId", (request, response) => data(response, store.getSetting(request.params.settingId)));
|
|
698
813
|
app.patch("/api/settings/:settingId", (request, response) => {
|
|
699
|
-
const { changeNote, ...input } = parse(settingSchema.partial().extend({ changeNote: changeNoteSchema }), request.body);
|
|
700
|
-
data(response, store.updateSetting(request.params.settingId, input, "manual", null, changeNote));
|
|
814
|
+
const { changeNote, expectedVersionNo, ...input } = parse(settingSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
815
|
+
data(response, store.updateSetting(request.params.settingId, input, "manual", null, changeNote, expectedVersionNo));
|
|
701
816
|
});
|
|
702
817
|
app.delete("/api/settings/:settingId", (request, response) => {
|
|
703
|
-
|
|
818
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
819
|
+
store.deleteSetting(request.params.settingId, input.expectedVersionNo);
|
|
704
820
|
noContent(response);
|
|
705
821
|
});
|
|
706
822
|
app.get("/api/works/:workId/characters", (request, response) => {
|
|
@@ -709,28 +825,54 @@ export function createRuntime(options) {
|
|
|
709
825
|
includeMerged: z.enum(["0", "1"]).default("0")
|
|
710
826
|
}), request.query);
|
|
711
827
|
const pagination = parsePagination(request.query);
|
|
712
|
-
|
|
828
|
+
const permissions = requestPermissions(request, request.params.workId);
|
|
829
|
+
const characters = pagination
|
|
713
830
|
? store.listCharactersPage(request.params.workId, pagination, includeSections === "true", includeMerged === "1")
|
|
714
|
-
: store.listCharacters(request.params.workId, includeSections === "true", includeMerged === "1")
|
|
831
|
+
: store.listCharacters(request.params.workId, includeSections === "true", includeMerged === "1");
|
|
832
|
+
data(response, mapRecords(characters, (character) => redactCharacterLinks(character, permissions)));
|
|
833
|
+
});
|
|
834
|
+
app.post("/api/works/:workId/characters", (request, response) => {
|
|
835
|
+
const character = store.createCharacter(request.params.workId, parse(characterSchema, request.body));
|
|
836
|
+
data(response, redactCharacterLinks(character, requestPermissions(request, request.params.workId)), 201);
|
|
837
|
+
});
|
|
838
|
+
app.get("/api/characters/:characterId", (request, response) => {
|
|
839
|
+
data(response, redactCharacterLinks(store.getCharacter(request.params.characterId), requestPermissions(request)));
|
|
715
840
|
});
|
|
716
|
-
app.post("/api/works/:workId/characters", (request, response) => data(response, store.createCharacter(request.params.workId, parse(characterSchema, request.body)), 201));
|
|
717
|
-
app.get("/api/characters/:characterId", (request, response) => data(response, store.getCharacter(request.params.characterId)));
|
|
718
841
|
app.patch("/api/characters/:characterId", (request, response) => {
|
|
719
|
-
const { changeNote, ...input } = parse(characterUpdateSchema, request.body);
|
|
720
|
-
|
|
842
|
+
const { changeNote, expectedVersionNo, ...input } = parse(characterUpdateSchema.extend({ expectedVersionNo: expectedVersionNoSchema }), request.body);
|
|
843
|
+
const character = store.updateCharacter(request.params.characterId, input, "manual", null, changeNote, expectedVersionNo);
|
|
844
|
+
data(response, redactCharacterLinks(character, requestPermissions(request)));
|
|
721
845
|
});
|
|
722
846
|
app.get("/api/characters/:characterId/versions", (request, response) => {
|
|
723
847
|
const pagination = parsePagination(request.query);
|
|
724
|
-
|
|
848
|
+
const versions = pagination ? store.listCharacterVersionsPage(request.params.characterId, pagination) : store.listCharacterVersions(request.params.characterId);
|
|
849
|
+
const permissions = requestPermissions(request);
|
|
850
|
+
data(response, redactVersionSnapshots(versions, (snapshot) => redactCharacterLinks(snapshot, permissions)));
|
|
725
851
|
});
|
|
726
852
|
app.post("/api/characters/:characterId/restore", (request, response) => {
|
|
727
|
-
const input = parse(z.object({ versionNo: z.number().int().positive() }), request.body);
|
|
728
|
-
|
|
853
|
+
const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
854
|
+
const character = store.restoreCharacter(request.params.characterId, input.versionNo, input.expectedVersionNo);
|
|
855
|
+
data(response, redactCharacterLinks(character, requestPermissions(request)));
|
|
729
856
|
});
|
|
730
857
|
app.delete("/api/characters/:characterId", (request, response) => {
|
|
731
|
-
|
|
858
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
859
|
+
store.deleteCharacter(request.params.characterId, input.expectedVersionNo);
|
|
732
860
|
noContent(response);
|
|
733
861
|
});
|
|
862
|
+
app.post("/api/characters/:characterId/merge", (request, response) => {
|
|
863
|
+
const input = parse(z.object({
|
|
864
|
+
targetCharacterId: identifier,
|
|
865
|
+
expectedTargetVersionNo: z.number().int().positive(),
|
|
866
|
+
expectedSourceVersionNo: z.number().int().positive()
|
|
867
|
+
}).strict(), request.body);
|
|
868
|
+
const result = store.mergeCharacters({
|
|
869
|
+
reviewId: null,
|
|
870
|
+
sourceCharacterId: request.params.characterId,
|
|
871
|
+
...input
|
|
872
|
+
});
|
|
873
|
+
const permissions = requestPermissions(request);
|
|
874
|
+
data(response, redactMergeRecords(result, (character) => redactCharacterLinks(character, permissions)));
|
|
875
|
+
});
|
|
734
876
|
app.get("/api/characters/:characterId/sections", (request, response) => {
|
|
735
877
|
const pagination = parsePagination(request.query);
|
|
736
878
|
data(response, pagination
|
|
@@ -744,11 +886,12 @@ export function createRuntime(options) {
|
|
|
744
886
|
data(response, store.getCharacterProfileSection(request.params.sectionId));
|
|
745
887
|
});
|
|
746
888
|
app.patch("/api/character-sections/:sectionId", (request, response) => {
|
|
747
|
-
const { changeNote, ...input } = parse(characterProfileSectionSchema.partial().extend({ changeNote: changeNoteSchema }).strict(), request.body);
|
|
748
|
-
data(response, store.updateCharacterProfileSection(request.params.sectionId, input, "manual", null, changeNote));
|
|
889
|
+
const { changeNote, expectedVersionNo, ...input } = parse(characterProfileSectionSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
890
|
+
data(response, store.updateCharacterProfileSection(request.params.sectionId, input, "manual", null, changeNote, expectedVersionNo));
|
|
749
891
|
});
|
|
750
892
|
app.delete("/api/character-sections/:sectionId", (request, response) => {
|
|
751
|
-
|
|
893
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
894
|
+
store.deleteCharacterProfileSection(request.params.sectionId, input.expectedVersionNo);
|
|
752
895
|
noContent(response);
|
|
753
896
|
});
|
|
754
897
|
app.get("/api/character-sections/:sectionId/versions", (request, response) => {
|
|
@@ -758,8 +901,8 @@ export function createRuntime(options) {
|
|
|
758
901
|
: store.listCharacterProfileSectionVersions(request.params.sectionId));
|
|
759
902
|
});
|
|
760
903
|
app.post("/api/character-sections/:sectionId/restore", (request, response) => {
|
|
761
|
-
const input = parse(z.object({ versionNo: z.number().int().positive() }).strict(), request.body);
|
|
762
|
-
data(response, store.restoreCharacterProfileSection(request.params.sectionId, input.versionNo));
|
|
904
|
+
const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
905
|
+
data(response, store.restoreCharacterProfileSection(request.params.sectionId, input.versionNo, input.expectedVersionNo));
|
|
763
906
|
});
|
|
764
907
|
app.get("/api/works/:workId/attachments", (request, response) => {
|
|
765
908
|
const pagination = parsePagination(request.query);
|
|
@@ -808,36 +951,62 @@ export function createRuntime(options) {
|
|
|
808
951
|
});
|
|
809
952
|
app.get("/api/works/:workId/races", (request, response) => {
|
|
810
953
|
const pagination = parsePagination(request.query);
|
|
811
|
-
|
|
954
|
+
const races = pagination ? store.listRacesPage(request.params.workId, pagination) : store.listRaces(request.params.workId);
|
|
955
|
+
const permissions = requestPermissions(request, request.params.workId);
|
|
956
|
+
data(response, mapRecords(races, (race) => redactRaceMembers(race, permissions)));
|
|
812
957
|
});
|
|
813
958
|
app.post("/api/works/:workId/races", (request, response) => {
|
|
814
|
-
|
|
959
|
+
const race = store.createRace(request.params.workId, parse(raceSchema, request.body));
|
|
960
|
+
data(response, redactRaceMembers(race, requestPermissions(request, request.params.workId)), 201);
|
|
961
|
+
});
|
|
962
|
+
app.get("/api/races/:raceId", (request, response) => {
|
|
963
|
+
data(response, redactRaceMembers(store.getRace(request.params.raceId), requestPermissions(request)));
|
|
815
964
|
});
|
|
816
|
-
app.get("/api/races/:raceId", (request, response) => data(response, store.getRace(request.params.raceId)));
|
|
817
965
|
app.patch("/api/races/:raceId", (request, response) => {
|
|
818
|
-
const { changeNote, ...input } = parse(raceSchema.partial().extend({ changeNote: changeNoteSchema }), request.body);
|
|
819
|
-
|
|
966
|
+
const { changeNote, expectedVersionNo, ...input } = parse(raceSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
967
|
+
const race = store.updateRace(request.params.raceId, input, "manual", null, changeNote, expectedVersionNo);
|
|
968
|
+
data(response, redactRaceMembers(race, requestPermissions(request)));
|
|
820
969
|
});
|
|
821
970
|
app.delete("/api/races/:raceId", (request, response) => {
|
|
822
|
-
|
|
971
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
972
|
+
store.deleteRace(request.params.raceId, input.expectedVersionNo);
|
|
823
973
|
noContent(response);
|
|
824
974
|
});
|
|
975
|
+
app.post("/api/races/:raceId/merge", (request, response) => {
|
|
976
|
+
const input = parse(z.object({ targetRaceId: identifier }).strict(), request.body);
|
|
977
|
+
const result = store.mergeRaces(request.params.raceId, input.targetRaceId);
|
|
978
|
+
const permissions = requestPermissions(request);
|
|
979
|
+
data(response, redactMergeRecords(result, (race) => redactRaceMembers(race, permissions)));
|
|
980
|
+
});
|
|
825
981
|
app.get("/api/works/:workId/organizations", (request, response) => {
|
|
826
982
|
const pagination = parsePagination(request.query);
|
|
827
|
-
|
|
983
|
+
const organizations = pagination ? store.listOrganizationsPage(request.params.workId, pagination) : store.listOrganizations(request.params.workId);
|
|
984
|
+
const permissions = requestPermissions(request, request.params.workId);
|
|
985
|
+
data(response, mapRecords(organizations, (organization) => redactOrganizationMembers(organization, permissions)));
|
|
828
986
|
});
|
|
829
987
|
app.post("/api/works/:workId/organizations", (request, response) => {
|
|
830
|
-
|
|
988
|
+
const organization = store.createOrganization(request.params.workId, parse(organizationSchema, request.body));
|
|
989
|
+
data(response, redactOrganizationMembers(organization, requestPermissions(request, request.params.workId)), 201);
|
|
990
|
+
});
|
|
991
|
+
app.get("/api/organizations/:organizationId", (request, response) => {
|
|
992
|
+
data(response, redactOrganizationMembers(store.getOrganization(request.params.organizationId), requestPermissions(request)));
|
|
831
993
|
});
|
|
832
|
-
app.get("/api/organizations/:organizationId", (request, response) => data(response, store.getOrganization(request.params.organizationId)));
|
|
833
994
|
app.patch("/api/organizations/:organizationId", (request, response) => {
|
|
834
|
-
const { changeNote, ...input } = parse(organizationSchema.partial().extend({ changeNote: changeNoteSchema }), request.body);
|
|
835
|
-
|
|
995
|
+
const { changeNote, expectedVersionNo, ...input } = parse(organizationSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
996
|
+
const organization = store.updateOrganization(request.params.organizationId, input, "manual", null, changeNote, expectedVersionNo);
|
|
997
|
+
data(response, redactOrganizationMembers(organization, requestPermissions(request)));
|
|
836
998
|
});
|
|
837
999
|
app.delete("/api/organizations/:organizationId", (request, response) => {
|
|
838
|
-
|
|
1000
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
1001
|
+
store.deleteOrganization(request.params.organizationId, input.expectedVersionNo);
|
|
839
1002
|
noContent(response);
|
|
840
1003
|
});
|
|
1004
|
+
app.post("/api/organizations/:organizationId/merge", (request, response) => {
|
|
1005
|
+
const input = parse(z.object({ targetOrganizationId: identifier }).strict(), request.body);
|
|
1006
|
+
const result = store.mergeOrganizations(request.params.organizationId, input.targetOrganizationId);
|
|
1007
|
+
const permissions = requestPermissions(request);
|
|
1008
|
+
data(response, redactMergeRecords(result, (organization) => redactOrganizationMembers(organization, permissions)));
|
|
1009
|
+
});
|
|
841
1010
|
app.get("/api/works/:workId/timeline-tracks", (request, response) => {
|
|
842
1011
|
const pagination = parsePagination(request.query);
|
|
843
1012
|
data(response, pagination ? store.listTimelineTracksPage(request.params.workId, pagination) : store.listTimelineTracks(request.params.workId));
|
|
@@ -845,11 +1014,12 @@ export function createRuntime(options) {
|
|
|
845
1014
|
app.post("/api/works/:workId/timeline-tracks", (request, response) => data(response, store.createTimelineTrack(request.params.workId, parse(timelineTrackSchema, request.body)), 201));
|
|
846
1015
|
app.get("/api/timeline-tracks/:trackId", (request, response) => data(response, store.getTimelineTrack(request.params.trackId)));
|
|
847
1016
|
app.patch("/api/timeline-tracks/:trackId", (request, response) => {
|
|
848
|
-
const { changeNote, ...input } = parse(timelineTrackSchema.partial().extend({ changeNote: changeNoteSchema }), request.body);
|
|
849
|
-
data(response, store.updateTimelineTrack(request.params.trackId, input, "manual", null, changeNote));
|
|
1017
|
+
const { changeNote, expectedVersionNo, ...input } = parse(timelineTrackSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
1018
|
+
data(response, store.updateTimelineTrack(request.params.trackId, input, "manual", null, changeNote, expectedVersionNo));
|
|
850
1019
|
});
|
|
851
1020
|
app.delete("/api/timeline-tracks/:trackId", (request, response) => {
|
|
852
|
-
|
|
1021
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
1022
|
+
store.deleteTimelineTrack(request.params.trackId, input.expectedVersionNo);
|
|
853
1023
|
noContent(response);
|
|
854
1024
|
});
|
|
855
1025
|
app.get("/api/works/:workId/timeline", (request, response) => {
|
|
@@ -863,14 +1033,16 @@ export function createRuntime(options) {
|
|
|
863
1033
|
name: nonEmpty.max(300),
|
|
864
1034
|
description: z.string().max(100_000).optional(),
|
|
865
1035
|
timeLabel: z.string().max(300).optional(),
|
|
866
|
-
timeSort: z.number().finite().nullable().optional()
|
|
867
|
-
|
|
868
|
-
|
|
1036
|
+
timeSort: z.number().finite().nullable().optional(),
|
|
1037
|
+
expectedVersionNos: z.record(identifier, z.number().int().positive()).optional()
|
|
1038
|
+
}).strict(), request.body);
|
|
1039
|
+
const { expectedVersionNos, ...mergeInput } = input;
|
|
1040
|
+
data(response, store.mergeTimelineEvents(request.params.workId, input.eventIds, mergeInput, expectedVersionNos), 201);
|
|
869
1041
|
});
|
|
870
1042
|
app.get("/api/timeline/:eventId", (request, response) => data(response, store.getTimelineEvent(request.params.eventId)));
|
|
871
1043
|
app.patch("/api/timeline/:eventId", (request, response) => {
|
|
872
|
-
const { changeNote, ...input } = parse(timelineSchema.partial().extend({ changeNote: changeNoteSchema }), request.body);
|
|
873
|
-
data(response, store.updateTimelineEvent(request.params.eventId, input, "manual", null, changeNote));
|
|
1044
|
+
const { changeNote, expectedVersionNo, ...input } = parse(timelineSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
1045
|
+
data(response, store.updateTimelineEvent(request.params.eventId, input, "manual", null, changeNote, expectedVersionNo));
|
|
874
1046
|
});
|
|
875
1047
|
app.post("/api/timeline/:eventId/split", (request, response) => {
|
|
876
1048
|
const input = parse(z.object({
|
|
@@ -879,12 +1051,14 @@ export function createRuntime(options) {
|
|
|
879
1051
|
description: z.string().max(100_000).optional(),
|
|
880
1052
|
timeLabel: z.string().max(300).optional(),
|
|
881
1053
|
timeSort: z.number().finite().nullable().optional()
|
|
882
|
-
})).min(2)
|
|
883
|
-
|
|
884
|
-
|
|
1054
|
+
})).min(2),
|
|
1055
|
+
expectedVersionNo: expectedVersionNoSchema
|
|
1056
|
+
}).strict(), request.body);
|
|
1057
|
+
data(response, store.splitTimelineEvent(request.params.eventId, input.parts, input.expectedVersionNo), 201);
|
|
885
1058
|
});
|
|
886
1059
|
app.delete("/api/timeline/:eventId", (request, response) => {
|
|
887
|
-
|
|
1060
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
1061
|
+
store.deleteTimelineEvent(request.params.eventId, input.expectedVersionNo);
|
|
888
1062
|
noContent(response);
|
|
889
1063
|
});
|
|
890
1064
|
app.get("/api/works/:workId/relationships", (request, response) => {
|
|
@@ -899,24 +1073,35 @@ export function createRuntime(options) {
|
|
|
899
1073
|
app.post("/api/works/:workId/relationships", (request, response) => data(response, store.createRelationship(request.params.workId, parse(relationshipSchema, request.body)), 201));
|
|
900
1074
|
app.get("/api/relationships/:relationshipId", (request, response) => data(response, store.getRelationship(request.params.relationshipId)));
|
|
901
1075
|
app.patch("/api/relationships/:relationshipId", (request, response) => {
|
|
902
|
-
const { changeNote, ...input } = parse(relationshipSchema.partial().extend({ changeNote: changeNoteSchema }), request.body);
|
|
903
|
-
data(response, store.updateRelationship(request.params.relationshipId, input, "manual", null, changeNote));
|
|
1076
|
+
const { changeNote, expectedVersionNo, ...input } = parse(relationshipSchema.partial().extend({ changeNote: changeNoteSchema, expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
1077
|
+
data(response, store.updateRelationship(request.params.relationshipId, input, "manual", null, changeNote, expectedVersionNo));
|
|
904
1078
|
});
|
|
905
1079
|
app.delete("/api/relationships/:relationshipId", (request, response) => {
|
|
906
|
-
|
|
1080
|
+
const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
|
|
1081
|
+
store.deleteRelationship(request.params.relationshipId, input.expectedVersionNo);
|
|
907
1082
|
noContent(response);
|
|
908
1083
|
});
|
|
909
1084
|
app.get("/api/entity-versions/:entityType/:entityId", (request, response) => {
|
|
910
1085
|
const input = parse(z.object({ entityType: versionedEntityTypeSchema, entityId: identifier }), request.params);
|
|
911
1086
|
const pagination = parsePagination(request.query);
|
|
912
|
-
|
|
1087
|
+
const versions = pagination
|
|
913
1088
|
? store.listEntityVersionsPage(input.entityType, input.entityId, pagination)
|
|
914
|
-
: store.listEntityVersions(input.entityType, input.entityId)
|
|
1089
|
+
: store.listEntityVersions(input.entityType, input.entityId);
|
|
1090
|
+
const permissions = requestPermissions(request);
|
|
1091
|
+
if (input.entityType === "race") {
|
|
1092
|
+
data(response, redactVersionSnapshots(versions, (snapshot) => redactRaceMembers(snapshot, permissions)));
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (input.entityType === "organization") {
|
|
1096
|
+
data(response, redactVersionSnapshots(versions, (snapshot) => redactOrganizationMembers(snapshot, permissions)));
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
data(response, versions);
|
|
915
1100
|
});
|
|
916
1101
|
app.post("/api/entity-versions/:entityType/:entityId/restore", (request, response) => {
|
|
917
1102
|
const params = parse(z.object({ entityType: versionedEntityTypeSchema, entityId: identifier }), request.params);
|
|
918
|
-
const input = parse(z.object({ versionNo: z.number().int().positive() }), request.body);
|
|
919
|
-
data(response, store.restoreEntityVersion(params.entityType, params.entityId, input.versionNo));
|
|
1103
|
+
const input = parse(z.object({ versionNo: z.number().int().positive(), expectedVersionNo: expectedVersionNoSchema }).strict(), request.body);
|
|
1104
|
+
data(response, store.restoreEntityVersion(params.entityType, params.entityId, input.versionNo, input.expectedVersionNo));
|
|
920
1105
|
});
|
|
921
1106
|
app.get("/api/works/:workId/reviews", (request, response) => {
|
|
922
1107
|
const status = typeof request.query.status === "string" ? request.query.status : undefined;
|