mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
fix: Reject collections.update requests that include both description and data (#12648)
This commit is contained in:
@@ -1257,6 +1257,23 @@ describe("#collections.create", () => {
|
||||
expect(res.status).toEqual(400);
|
||||
});
|
||||
|
||||
it("rejects providing both description and data", async () => {
|
||||
const user = await buildUser();
|
||||
const res = await server.post("/api/collections.create", user, {
|
||||
body: {
|
||||
name: "Test",
|
||||
description: "Test",
|
||||
data: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{ type: "paragraph", content: [{ type: "text", text: "Test" }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res.status).toEqual(400);
|
||||
});
|
||||
|
||||
it("should allow setting sharing to false", async () => {
|
||||
const user = await buildUser();
|
||||
const res = await server.post("/api/collections.create", user, {
|
||||
@@ -1448,6 +1465,50 @@ describe("#collections.update", () => {
|
||||
expect(collection.content).toBeTruthy();
|
||||
});
|
||||
|
||||
it("replaces rendered content when description is updated post-create", async () => {
|
||||
const team = await buildTeam();
|
||||
const admin = await buildAdmin({ teamId: team.id });
|
||||
|
||||
const createRes = await server.post("/api/collections.create", admin, {
|
||||
headers: { "x-api-version": "3" },
|
||||
body: { name: "Foo", description: "Original" },
|
||||
});
|
||||
const { id } = (await createRes.json()).data;
|
||||
|
||||
const updateRes = await server.post("/api/collections.update", admin, {
|
||||
headers: { "x-api-version": "3" },
|
||||
body: { id, description: "Replaced" },
|
||||
});
|
||||
expect(updateRes.status).toEqual(200);
|
||||
|
||||
const infoRes = await server.post("/api/collections.info", admin, {
|
||||
headers: { "x-api-version": "3" },
|
||||
body: { id },
|
||||
});
|
||||
const content = JSON.stringify((await infoRes.json()).data.data);
|
||||
expect(content).toContain("Replaced");
|
||||
expect(content).not.toContain("Original");
|
||||
});
|
||||
|
||||
it("rejects providing both description and data", async () => {
|
||||
const team = await buildTeam();
|
||||
const admin = await buildAdmin({ teamId: team.id });
|
||||
const collection = await buildCollection({ teamId: team.id });
|
||||
const res = await server.post("/api/collections.update", admin, {
|
||||
body: {
|
||||
id: collection.id,
|
||||
description: "Test",
|
||||
data: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{ type: "paragraph", content: [{ type: "text", text: "Test" }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res.status).toEqual(400);
|
||||
});
|
||||
|
||||
it("allows editing data", async () => {
|
||||
const team = await buildTeam();
|
||||
const admin = await buildAdmin({ teamId: team.id });
|
||||
|
||||
@@ -15,39 +15,50 @@ const BaseIdSchema = z.object({
|
||||
id: zodIdType(),
|
||||
});
|
||||
|
||||
/** The landing page can be set from description (markdown) or data (rich content), but not both. */
|
||||
const refineBodyContent = <T extends { description?: unknown; data?: unknown }>(
|
||||
body: T
|
||||
) => isUndefined(body.description) || isUndefined(body.data);
|
||||
|
||||
const bodyContentError = {
|
||||
error: "Only one of description or data may be provided",
|
||||
};
|
||||
|
||||
export const CollectionsCreateSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
name: z.string(),
|
||||
color: z
|
||||
.string()
|
||||
.regex(ValidateColor.regex, { message: ValidateColor.message })
|
||||
.nullish(),
|
||||
description: z.string().nullish(),
|
||||
data: ProsemirrorSchema({ allowEmpty: true }).nullish(),
|
||||
permission: z
|
||||
.enum(CollectionPermission)
|
||||
.nullish()
|
||||
.transform((val) => (isUndefined(val) ? null : val)),
|
||||
sharing: z.boolean().prefault(true),
|
||||
icon: zodIconType().optional(),
|
||||
sort: z
|
||||
.object({
|
||||
field: z.union([z.literal("title"), z.literal("index")]),
|
||||
direction: z.union([z.literal("asc"), z.literal("desc")]),
|
||||
})
|
||||
.prefault(Collection.DEFAULT_SORT),
|
||||
index: z
|
||||
.string()
|
||||
.regex(ValidateIndex.regex, { message: ValidateIndex.message })
|
||||
.max(ValidateIndex.maxLength, {
|
||||
message: `Must be ${ValidateIndex.maxLength} or fewer characters long`,
|
||||
})
|
||||
.optional(),
|
||||
commenting: z.boolean().nullish(),
|
||||
templateManagement: z
|
||||
.enum([CollectionPermission.Admin, CollectionPermission.ReadWrite])
|
||||
.prefault(CollectionPermission.Admin),
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
color: z
|
||||
.string()
|
||||
.regex(ValidateColor.regex, { message: ValidateColor.message })
|
||||
.nullish(),
|
||||
description: z.string().nullish(),
|
||||
data: ProsemirrorSchema({ allowEmpty: true }).nullish(),
|
||||
permission: z
|
||||
.enum(CollectionPermission)
|
||||
.nullish()
|
||||
.transform((val) => (isUndefined(val) ? null : val)),
|
||||
sharing: z.boolean().prefault(true),
|
||||
icon: zodIconType().optional(),
|
||||
sort: z
|
||||
.object({
|
||||
field: z.union([z.literal("title"), z.literal("index")]),
|
||||
direction: z.union([z.literal("asc"), z.literal("desc")]),
|
||||
})
|
||||
.prefault(Collection.DEFAULT_SORT),
|
||||
index: z
|
||||
.string()
|
||||
.regex(ValidateIndex.regex, { message: ValidateIndex.message })
|
||||
.max(ValidateIndex.maxLength, {
|
||||
message: `Must be ${ValidateIndex.maxLength} or fewer characters long`,
|
||||
})
|
||||
.optional(),
|
||||
commenting: z.boolean().nullish(),
|
||||
templateManagement: z
|
||||
.enum([CollectionPermission.Admin, CollectionPermission.ReadWrite])
|
||||
.prefault(CollectionPermission.Admin),
|
||||
})
|
||||
.refine(refineBodyContent, bodyContentError),
|
||||
});
|
||||
|
||||
export type CollectionsCreateReq = z.infer<typeof CollectionsCreateSchema>;
|
||||
@@ -188,7 +199,7 @@ export const CollectionsUpdateSchema = BaseSchema.extend({
|
||||
templateManagement: z
|
||||
.enum([CollectionPermission.Admin, CollectionPermission.ReadWrite])
|
||||
.optional(),
|
||||
}),
|
||||
}).refine(refineBodyContent, bodyContentError),
|
||||
});
|
||||
|
||||
export type CollectionsUpdateReq = z.infer<typeof CollectionsUpdateSchema>;
|
||||
|
||||
Reference in New Issue
Block a user