Move collection description rendering to JSON (#6944)
* First pass, moving collection description rendering to JSON * tsc * docs * refactor * test
This commit is contained in:
@@ -1318,6 +1318,48 @@ describe("#collections.update", () => {
|
||||
expect(body.policies.length).toBe(1);
|
||||
});
|
||||
|
||||
it("allows editing description", 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", {
|
||||
body: {
|
||||
token: admin.getJwtToken(),
|
||||
id: collection.id,
|
||||
description: "Test",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.description).toBe("Test");
|
||||
|
||||
await collection.reload();
|
||||
|
||||
expect(collection.description).toBe("Test");
|
||||
expect(collection.content).toBeTruthy();
|
||||
});
|
||||
|
||||
it("allows editing 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", {
|
||||
body: {
|
||||
token: admin.getJwtToken(),
|
||||
id: collection.id,
|
||||
data: {
|
||||
content: [
|
||||
{ content: [{ text: "Test", type: "text" }], type: "paragraph" },
|
||||
],
|
||||
type: "doc",
|
||||
},
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.description).toBe("Test");
|
||||
});
|
||||
|
||||
it("allows editing sort", async () => {
|
||||
const team = await buildTeam();
|
||||
const admin = await buildAdmin({ teamId: team.id });
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import collectionDestroyer from "@server/commands/collectionDestroyer";
|
||||
import collectionExporter from "@server/commands/collectionExporter";
|
||||
import teamUpdater from "@server/commands/teamUpdater";
|
||||
import { parser } from "@server/editor";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { rateLimiter } from "@server/middlewares/rateLimiter";
|
||||
import { transaction } from "@server/middlewares/transaction";
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
Attachment,
|
||||
FileOperation,
|
||||
} from "@server/models";
|
||||
import { DocumentHelper } from "@server/models/helpers/DocumentHelper";
|
||||
import { authorize } from "@server/policies";
|
||||
import {
|
||||
presentCollection,
|
||||
@@ -48,8 +50,10 @@ router.post(
|
||||
"collections.create",
|
||||
auth(),
|
||||
validate(T.CollectionsCreateSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.CollectionsCreateReq>) => {
|
||||
const { name, color, description, permission, sharing, icon, sort } =
|
||||
const { transaction } = ctx.state;
|
||||
const { name, color, description, data, permission, sharing, icon, sort } =
|
||||
ctx.input.body;
|
||||
let { index } = ctx.input.body;
|
||||
|
||||
@@ -69,6 +73,7 @@ router.post(
|
||||
Sequelize.literal('"collection"."index" collate "C"'),
|
||||
["updatedAt", "DESC"],
|
||||
],
|
||||
transaction,
|
||||
});
|
||||
|
||||
index = fractionalIndex(
|
||||
@@ -78,9 +83,10 @@ router.post(
|
||||
}
|
||||
|
||||
index = await removeIndexCollision(user.teamId, index);
|
||||
const collection = await Collection.create({
|
||||
const collection = Collection.build({
|
||||
name,
|
||||
description,
|
||||
content: data,
|
||||
description: data ? undefined : description,
|
||||
icon,
|
||||
color,
|
||||
teamId: user.teamId,
|
||||
@@ -90,24 +96,38 @@ router.post(
|
||||
sort,
|
||||
index,
|
||||
});
|
||||
await Event.create({
|
||||
name: "collections.create",
|
||||
collectionId: collection.id,
|
||||
teamId: collection.teamId,
|
||||
actorId: user.id,
|
||||
data: {
|
||||
name,
|
||||
|
||||
if (data) {
|
||||
collection.description = DocumentHelper.toMarkdown(collection);
|
||||
}
|
||||
|
||||
await collection.save({ transaction });
|
||||
|
||||
await Event.create(
|
||||
{
|
||||
name: "collections.create",
|
||||
collectionId: collection.id,
|
||||
teamId: collection.teamId,
|
||||
actorId: user.id,
|
||||
data: {
|
||||
name,
|
||||
},
|
||||
ip: ctx.request.ip,
|
||||
},
|
||||
ip: ctx.request.ip,
|
||||
});
|
||||
{
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
// we must reload the collection to get memberships for policy presenter
|
||||
const reloaded = await Collection.scope({
|
||||
method: ["withMembership", user.id],
|
||||
}).findByPk(collection.id);
|
||||
}).findByPk(collection.id, {
|
||||
transaction,
|
||||
});
|
||||
invariant(reloaded, "collection not found");
|
||||
|
||||
ctx.body = {
|
||||
data: presentCollection(reloaded),
|
||||
data: await presentCollection(ctx, reloaded),
|
||||
policies: presentPolicies(user, [reloaded]),
|
||||
};
|
||||
}
|
||||
@@ -127,7 +147,7 @@ router.post(
|
||||
authorize(user, "read", collection);
|
||||
|
||||
ctx.body = {
|
||||
data: presentCollection(collection),
|
||||
data: await presentCollection(ctx, collection),
|
||||
policies: presentPolicies(user, [collection]),
|
||||
};
|
||||
}
|
||||
@@ -636,8 +656,17 @@ router.post(
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.CollectionsUpdateReq>) => {
|
||||
const { transaction } = ctx.state;
|
||||
const { id, name, description, icon, permission, color, sort, sharing } =
|
||||
ctx.input.body;
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
data,
|
||||
icon,
|
||||
permission,
|
||||
color,
|
||||
sort,
|
||||
sharing,
|
||||
} = ctx.input.body;
|
||||
|
||||
const { user } = ctx.state.auth;
|
||||
const collection = await Collection.scope({
|
||||
@@ -675,6 +704,14 @@ router.post(
|
||||
|
||||
if (description !== undefined) {
|
||||
collection.description = description;
|
||||
collection.content = description
|
||||
? parser.parse(description)?.toJSON()
|
||||
: null;
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
collection.content = data;
|
||||
collection.description = DocumentHelper.toMarkdown(collection);
|
||||
}
|
||||
|
||||
if (icon !== undefined) {
|
||||
@@ -759,7 +796,7 @@ router.post(
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
data: presentCollection(collection),
|
||||
data: await presentCollection(ctx, collection),
|
||||
policies: presentPolicies(user, [collection]),
|
||||
};
|
||||
}
|
||||
@@ -811,7 +848,9 @@ router.post(
|
||||
|
||||
ctx.body = {
|
||||
pagination: { ...ctx.state.pagination, total },
|
||||
data: collections.map(presentCollection),
|
||||
data: await Promise.all(
|
||||
collections.map((collection) => presentCollection(ctx, collection))
|
||||
),
|
||||
policies: presentPolicies(user, collections),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { IconLibrary } from "@shared/utils/IconLibrary";
|
||||
import { colorPalette } from "@shared/utils/collections";
|
||||
import { Collection } from "@server/models";
|
||||
import { ValidateColor, ValidateIndex } from "@server/validation";
|
||||
import { BaseSchema } from "../schema";
|
||||
import { BaseSchema, ProsemirrorSchema } from "../schema";
|
||||
|
||||
function zodEnumFromObjectKeys<
|
||||
TI extends Record<string, any>,
|
||||
@@ -16,6 +16,11 @@ function zodEnumFromObjectKeys<
|
||||
return z.enum([firstKey, ...otherKeys]);
|
||||
}
|
||||
|
||||
const BaseIdSchema = z.object({
|
||||
/** Id of the collection to be updated */
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export const CollectionsCreateSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
name: z.string(),
|
||||
@@ -24,6 +29,7 @@ export const CollectionsCreateSchema = BaseSchema.extend({
|
||||
.regex(ValidateColor.regex, { message: ValidateColor.message })
|
||||
.default(randomElement(colorPalette)),
|
||||
description: z.string().nullish(),
|
||||
data: ProsemirrorSchema.nullish(),
|
||||
permission: z
|
||||
.nativeEnum(CollectionPermission)
|
||||
.nullish()
|
||||
@@ -49,17 +55,13 @@ export const CollectionsCreateSchema = BaseSchema.extend({
|
||||
export type CollectionsCreateReq = z.infer<typeof CollectionsCreateSchema>;
|
||||
|
||||
export const CollectionsInfoSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
}),
|
||||
body: BaseIdSchema,
|
||||
});
|
||||
|
||||
export type CollectionsInfoReq = z.infer<typeof CollectionsInfoSchema>;
|
||||
|
||||
export const CollectionsDocumentsSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
}),
|
||||
body: BaseIdSchema,
|
||||
});
|
||||
|
||||
export type CollectionsDocumentsReq = z.infer<
|
||||
@@ -82,8 +84,7 @@ export const CollectionsImportSchema = BaseSchema.extend({
|
||||
export type CollectionsImportReq = z.infer<typeof CollectionsImportSchema>;
|
||||
|
||||
export const CollectionsAddGroupSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
groupId: z.string().uuid(),
|
||||
permission: z
|
||||
.nativeEnum(CollectionPermission)
|
||||
@@ -94,8 +95,7 @@ export const CollectionsAddGroupSchema = BaseSchema.extend({
|
||||
export type CollectionsAddGroupsReq = z.infer<typeof CollectionsAddGroupSchema>;
|
||||
|
||||
export const CollectionsRemoveGroupSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
groupId: z.string().uuid(),
|
||||
}),
|
||||
});
|
||||
@@ -105,8 +105,7 @@ export type CollectionsRemoveGroupReq = z.infer<
|
||||
>;
|
||||
|
||||
export const CollectionsGroupMembershipsSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
query: z.string().optional(),
|
||||
permission: z.nativeEnum(CollectionPermission).optional(),
|
||||
}),
|
||||
@@ -117,8 +116,7 @@ export type CollectionsGroupMembershipsReq = z.infer<
|
||||
>;
|
||||
|
||||
export const CollectionsAddUserSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
userId: z.string().uuid(),
|
||||
permission: z.nativeEnum(CollectionPermission).optional(),
|
||||
}),
|
||||
@@ -127,8 +125,7 @@ export const CollectionsAddUserSchema = BaseSchema.extend({
|
||||
export type CollectionsAddUserReq = z.infer<typeof CollectionsAddUserSchema>;
|
||||
|
||||
export const CollectionsRemoveUserSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
userId: z.string().uuid(),
|
||||
}),
|
||||
});
|
||||
@@ -138,8 +135,7 @@ export type CollectionsRemoveUserReq = z.infer<
|
||||
>;
|
||||
|
||||
export const CollectionsMembershipsSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
query: z.string().optional(),
|
||||
permission: z.nativeEnum(CollectionPermission).optional(),
|
||||
}),
|
||||
@@ -150,8 +146,7 @@ export type CollectionsMembershipsReq = z.infer<
|
||||
>;
|
||||
|
||||
export const CollectionsExportSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
format: z
|
||||
.nativeEnum(FileOperationFormat)
|
||||
.default(FileOperationFormat.MarkdownZip),
|
||||
@@ -175,10 +170,10 @@ export type CollectionsExportAllReq = z.infer<
|
||||
>;
|
||||
|
||||
export const CollectionsUpdateSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
name: z.string().optional(),
|
||||
description: z.string().nullish(),
|
||||
data: ProsemirrorSchema.nullish(),
|
||||
icon: zodEnumFromObjectKeys(IconLibrary.mapping).nullish(),
|
||||
permission: z.nativeEnum(CollectionPermission).nullish(),
|
||||
color: z
|
||||
@@ -206,16 +201,13 @@ export const CollectionsListSchema = BaseSchema.extend({
|
||||
export type CollectionsListReq = z.infer<typeof CollectionsListSchema>;
|
||||
|
||||
export const CollectionsDeleteSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
}),
|
||||
body: BaseIdSchema,
|
||||
});
|
||||
|
||||
export type CollectionsDeleteReq = z.infer<typeof CollectionsDeleteSchema>;
|
||||
|
||||
export const CollectionsMoveSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
body: BaseIdSchema.extend({
|
||||
index: z
|
||||
.string()
|
||||
.regex(ValidateIndex.regex, { message: ValidateIndex.message })
|
||||
|
||||
@@ -1172,7 +1172,7 @@ router.post(
|
||||
documents.map((document) => presentDocument(ctx, document))
|
||||
),
|
||||
collections: await Promise.all(
|
||||
collections.map((collection) => presentCollection(collection))
|
||||
collections.map((collection) => presentCollection(ctx, collection))
|
||||
),
|
||||
},
|
||||
policies: collectionChanged ? presentPolicies(user, documents) : [],
|
||||
|
||||
Reference in New Issue
Block a user