Introduce zod for server-side validations (#4397)
* chore(server): use zod for validations * fix(server): use ctx.input for documents.list * fix(server): schema for documents.archived * fix(server): documents.deleted, documents.viewed & documents.drafts * fix(server): documents.info * fix(server): documents.export & documents.restore * fix(server): documents.search_titles & documents.search * fix(server): documents.templatize * fix(server): replace nullish() with optional() * fix(server): documents.update * fix(server): documents.move * fix(server): remaining * fix(server): add validation for snippet min and max words * fix(server): fix update types * fix(server): remove DocumentSchema * fix(server): collate duplicate schemas * fix: typos * fix: reviews * chore: Fixed case of Metrics import * fix: restructure /api * fix: loosen validation for id as it can be a slug too * Add test for query by slug Simplify import Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -64,8 +64,8 @@ Object {
|
||||
|
||||
exports[`#documents.update should require text while appending 1`] = `
|
||||
Object {
|
||||
"error": "param_required",
|
||||
"message": "Text is required while appending",
|
||||
"error": "validation_error",
|
||||
"message": "ValidationError: text is required while appending",
|
||||
"ok": false,
|
||||
"status": 400,
|
||||
}
|
||||
@@ -23,6 +23,17 @@ import { seed, getTestServer } from "@server/test/support";
|
||||
const server = getTestServer();
|
||||
|
||||
describe("#documents.info", () => {
|
||||
it("should fail if both id and shareId are absent", async () => {
|
||||
const res = await server.post("/api/documents.info", {
|
||||
body: {},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual(
|
||||
"ValidationError: one of id or shareId is required"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return published document", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.info", {
|
||||
@@ -36,6 +47,19 @@ describe("#documents.info", () => {
|
||||
expect(body.data.id).toEqual(document.id);
|
||||
});
|
||||
|
||||
it("should return published document for urlId", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.info", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: document.urlId,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.id).toEqual(document.id);
|
||||
});
|
||||
|
||||
it("should return archived document", async () => {
|
||||
const { user, document } = await seed();
|
||||
await document.archive(user.id);
|
||||
@@ -411,7 +435,7 @@ describe("#documents.info", () => {
|
||||
const res = await server.post("/api/documents.info", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: "test",
|
||||
id: "9bcbf864-1090-4eb6-ba05-4da0c3a5c58e",
|
||||
},
|
||||
});
|
||||
expect(res.status).toEqual(404);
|
||||
@@ -665,6 +689,58 @@ describe("#documents.export", () => {
|
||||
});
|
||||
|
||||
describe("#documents.list", () => {
|
||||
it("should fail for invalid userId", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
userId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("userId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should fail for invalid collectionId", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
collectionId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("collectionId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should fail for invalid parentDocumentId", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
parentDocumentId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("parentDocumentId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should fail for invalid backlinkDocumentId", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
backlinkDocumentId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("backlinkDocumentId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should return documents", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.list", {
|
||||
@@ -853,6 +929,36 @@ describe("#documents.list", () => {
|
||||
});
|
||||
|
||||
describe("#documents.drafts", () => {
|
||||
it("should fail for invalid collectionId", async () => {
|
||||
const { user, document } = await seed();
|
||||
document.publishedAt = null;
|
||||
await document.save();
|
||||
const res = await server.post("/api/documents.drafts", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
collectionId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("collectionId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should fail for invalid dateFilter", async () => {
|
||||
const { user, document } = await seed();
|
||||
document.publishedAt = null;
|
||||
await document.save();
|
||||
const res = await server.post("/api/documents.drafts", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
dateFilter: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("dateFilter: Invalid input");
|
||||
});
|
||||
|
||||
it("should return unpublished documents", async () => {
|
||||
const { user, document } = await seed();
|
||||
document.publishedAt = null;
|
||||
@@ -909,6 +1015,18 @@ describe("#documents.drafts", () => {
|
||||
});
|
||||
|
||||
describe("#documents.search_titles", () => {
|
||||
it("should fail without query", async () => {
|
||||
const user = await buildUser();
|
||||
const res = await server.post("/api/documents.search_titles", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("query: Required");
|
||||
});
|
||||
|
||||
it("should return case insensitive results for partial query", async () => {
|
||||
const user = await buildUser();
|
||||
const document = await buildDocument({
|
||||
@@ -960,6 +1078,20 @@ describe("#documents.search_titles", () => {
|
||||
});
|
||||
|
||||
describe("#documents.search", () => {
|
||||
it("should fail for invalid shareId", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.search", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
query: "much",
|
||||
shareId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("shareId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should return results", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.search", {
|
||||
@@ -1451,6 +1583,20 @@ describe("#documents.search", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#documents.templatize", () => {
|
||||
it("should require id", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.templatize", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toBe(400);
|
||||
expect(body.message).toBe("id: Required");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#documents.archived", () => {
|
||||
it("should return archived documents", async () => {
|
||||
const { user } = await seed();
|
||||
@@ -1675,6 +1821,64 @@ describe("#documents.viewed", () => {
|
||||
});
|
||||
|
||||
describe("#documents.move", () => {
|
||||
it("should fail if attempting to nest doc within itself", async () => {
|
||||
const { user, document } = await seed();
|
||||
const collection = await buildCollection();
|
||||
const res = await server.post("/api/documents.move", {
|
||||
body: {
|
||||
id: document.id,
|
||||
collectionId: collection.id,
|
||||
parentDocumentId: document.id,
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual(
|
||||
"ValidationError: infinite loop detected, cannot nest a document inside itself"
|
||||
);
|
||||
});
|
||||
|
||||
it("should require id", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.move", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("id: Required");
|
||||
});
|
||||
|
||||
it("should require collectionId", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.move", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: document.id,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("collectionId: Required");
|
||||
});
|
||||
|
||||
it("should fail for invalid index", async () => {
|
||||
const { user, document, collection } = await seed();
|
||||
const res = await server.post("/api/documents.move", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: document.id,
|
||||
collectionId: collection.id,
|
||||
index: -1,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("index: Number must be greater than 0");
|
||||
});
|
||||
|
||||
it("should move the document", async () => {
|
||||
const { user, document } = await seed();
|
||||
const collection = await buildCollection({
|
||||
@@ -1726,6 +1930,34 @@ describe("#documents.move", () => {
|
||||
});
|
||||
|
||||
describe("#documents.restore", () => {
|
||||
it("should require id", async () => {
|
||||
const { user, document } = await seed();
|
||||
await document.destroy();
|
||||
const res = await server.post("/api/documents.restore", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("id: Required");
|
||||
});
|
||||
|
||||
it("should fail for invalid collectionId", async () => {
|
||||
const { user, document } = await seed();
|
||||
await document.destroy();
|
||||
const res = await server.post("/api/documents.restore", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: document.id,
|
||||
collectionId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("collectionId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should allow restore of trashed documents", async () => {
|
||||
const { user, document } = await seed();
|
||||
await document.destroy();
|
||||
@@ -1905,7 +2137,7 @@ describe("#documents.restore", () => {
|
||||
const res = await server.post("/api/documents.restore", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: "test",
|
||||
id: "76fe8ba4-4e6a-4a75-8a10-9bf57330b24c",
|
||||
},
|
||||
});
|
||||
expect(res.status).toEqual(404);
|
||||
@@ -1935,6 +2167,18 @@ describe("#documents.restore", () => {
|
||||
});
|
||||
|
||||
describe("#documents.import", () => {
|
||||
it("should require collectionId", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.import", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("collectionId: Required");
|
||||
});
|
||||
|
||||
it("should error if no file is passed", async () => {
|
||||
const user = await buildUser();
|
||||
const res = await server.post("/api/documents.import", {
|
||||
@@ -1957,6 +2201,37 @@ describe("#documents.import", () => {
|
||||
});
|
||||
|
||||
describe("#documents.create", () => {
|
||||
it("should fail for invalid collectionId", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.create", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
collectionId: "invalid",
|
||||
title: "new document",
|
||||
text: "hello",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("collectionId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should fail for invalid parentDocumentId", async () => {
|
||||
const { user, collection } = await seed();
|
||||
const res = await server.post("/api/documents.create", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
collectionId: collection.id,
|
||||
parentDocumentId: "invalid",
|
||||
title: "new document",
|
||||
text: "hello",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("parentDocumentId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should create as a new document", async () => {
|
||||
const { user, collection } = await seed();
|
||||
const res = await server.post("/api/documents.create", {
|
||||
@@ -2007,10 +2282,10 @@ describe("#documents.create", () => {
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(body.message).toBe(
|
||||
"collectionId is required to create a nested doc or a template"
|
||||
);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toBe(
|
||||
"ValidationError: collectionId is required to create a template document"
|
||||
);
|
||||
});
|
||||
|
||||
it("should not allow publishing without specifying the collection", async () => {
|
||||
@@ -2026,10 +2301,10 @@ describe("#documents.create", () => {
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(body.message).toBe(
|
||||
"collectionId is required to publish a draft without collection"
|
||||
);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toBe(
|
||||
"ValidationError: collectionId is required to publish"
|
||||
);
|
||||
});
|
||||
|
||||
it("should not allow creating a nested doc without a collection", async () => {
|
||||
@@ -2045,10 +2320,10 @@ describe("#documents.create", () => {
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(body.message).toBe(
|
||||
"collectionId is required to create a nested doc or a template"
|
||||
);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toBe(
|
||||
"ValidationError: collectionId is required to create a nested document"
|
||||
);
|
||||
});
|
||||
|
||||
it("should not allow very long titles", async () => {
|
||||
@@ -2494,9 +2769,50 @@ describe("#documents.update", () => {
|
||||
});
|
||||
expect(res.status).toEqual(403);
|
||||
});
|
||||
|
||||
it("should fail for invalid collectionId", async () => {
|
||||
const { document } = await seed();
|
||||
const user = await buildUser();
|
||||
const res = await server.post("/api/documents.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: document.id,
|
||||
text: "Updated",
|
||||
collectionId: "invalid",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toBe(400);
|
||||
expect(body.message).toBe("collectionId: Invalid uuid");
|
||||
});
|
||||
|
||||
it("should require id", async () => {
|
||||
const user = await buildUser();
|
||||
const res = await server.post("/api/documents.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
text: "Updated",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toBe(400);
|
||||
expect(body.message).toBe("id: Required");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#documents.archive", () => {
|
||||
it("should require id", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.archive", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("id: Required");
|
||||
});
|
||||
|
||||
it("should allow archiving document", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.archive", {
|
||||
@@ -2523,6 +2839,18 @@ describe("#documents.archive", () => {
|
||||
});
|
||||
|
||||
describe("#documents.delete", () => {
|
||||
it("should require id", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.delete", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("id: Required");
|
||||
});
|
||||
|
||||
it("should allow deleting document", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.delete", {
|
||||
@@ -2616,6 +2944,18 @@ describe("#documents.delete", () => {
|
||||
});
|
||||
|
||||
describe("#documents.unpublish", () => {
|
||||
it("should require id", async () => {
|
||||
const { user } = await seed();
|
||||
const res = await server.post("/api/documents.unpublish", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(res.status).toEqual(400);
|
||||
expect(body.message).toEqual("id: Required");
|
||||
});
|
||||
|
||||
it("should unpublish a document", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.unpublish", {
|
||||
1236
server/routes/api/documents/documents.ts
Normal file
1236
server/routes/api/documents/documents.ts
Normal file
File diff suppressed because it is too large
Load Diff
1
server/routes/api/documents/index.ts
Normal file
1
server/routes/api/documents/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./documents";
|
||||
279
server/routes/api/documents/schema.ts
Normal file
279
server/routes/api/documents/schema.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
import { isEmpty } from "lodash";
|
||||
import { z } from "zod";
|
||||
|
||||
const DocumentsSortParamsSchema = z.object({
|
||||
/** Specifies the attributes by which documents will be sorted in the list */
|
||||
sort: z
|
||||
.string()
|
||||
.refine((val) => ["createdAt", "updatedAt", "index"].includes(val))
|
||||
.default("updatedAt"),
|
||||
|
||||
/** Specifies the sort order with respect to sort field */
|
||||
direction: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((val) => (val !== "ASC" ? "DESC" : val)),
|
||||
});
|
||||
|
||||
const DateFilterSchema = z.object({
|
||||
/** Date filter */
|
||||
dateFilter: z
|
||||
.union([
|
||||
z.literal("day"),
|
||||
z.literal("week"),
|
||||
z.literal("month"),
|
||||
z.literal("year"),
|
||||
])
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const SearchQuerySchema = z.object({
|
||||
/** Query for search */
|
||||
query: z.string().refine((v) => v.trim() !== ""),
|
||||
});
|
||||
|
||||
const BaseIdSchema = z.object({
|
||||
/** Id of the entity */
|
||||
id: z.string().uuid(),
|
||||
});
|
||||
|
||||
export const DocumentsListSchema = DocumentsSortParamsSchema.extend({
|
||||
/** Id of the user who created the doc */
|
||||
userId: z.string().uuid().optional(),
|
||||
|
||||
/** Alias for userId - kept for backwards compatibility */
|
||||
user: z.string().uuid().optional(),
|
||||
|
||||
/** Id of the collection to which the document belongs */
|
||||
collectionId: z.string().uuid().optional(),
|
||||
|
||||
/** Alias for collectionId - kept for backwards compatibility */
|
||||
collection: z.string().uuid().optional(),
|
||||
|
||||
/** Id of the backlinked document */
|
||||
backlinkDocumentId: z.string().uuid().optional(),
|
||||
|
||||
/** Id of the parent document to which the document belongs */
|
||||
parentDocumentId: z.string().uuid().nullish(),
|
||||
|
||||
/** Boolean which denotes whether the document is a template */
|
||||
template: z.boolean().optional(),
|
||||
})
|
||||
// Maintains backwards compatibility
|
||||
.transform((doc) => {
|
||||
doc.collectionId = doc.collectionId || doc.collection;
|
||||
doc.userId = doc.userId || doc.user;
|
||||
delete doc.collection;
|
||||
delete doc.user;
|
||||
|
||||
return doc;
|
||||
});
|
||||
|
||||
export type DocumentsListReq = z.infer<typeof DocumentsListSchema>;
|
||||
|
||||
export const DocumentsArchivedSchema = DocumentsSortParamsSchema.extend({});
|
||||
|
||||
export type DocumentsArchivedReq = z.infer<typeof DocumentsArchivedSchema>;
|
||||
|
||||
export const DocumentsDeletedSchema = DocumentsSortParamsSchema.extend({});
|
||||
|
||||
export type DocumentsDeletedReq = z.infer<typeof DocumentsDeletedSchema>;
|
||||
|
||||
export const DocumentsViewedSchema = DocumentsSortParamsSchema.extend({});
|
||||
|
||||
export type DocumentsViewedReq = z.infer<typeof DocumentsViewedSchema>;
|
||||
|
||||
export const DocumentsDraftsSchema = DocumentsSortParamsSchema.merge(
|
||||
DateFilterSchema
|
||||
).extend({
|
||||
/** Id of the collection to which the document belongs */
|
||||
collectionId: z.string().uuid().optional(),
|
||||
});
|
||||
|
||||
export type DocumentsDraftsReq = z.infer<typeof DocumentsDraftsSchema>;
|
||||
|
||||
export const DocumentsInfoSchema = z
|
||||
.object({
|
||||
/** Id of the document to be retrieved */
|
||||
id: z.string().optional(),
|
||||
|
||||
/** Share Id, if available */
|
||||
shareId: z.string().uuid().optional(),
|
||||
|
||||
/** Version of the API to be used */
|
||||
apiVersion: z.number().optional(),
|
||||
})
|
||||
.refine((obj) => !(isEmpty(obj.id) && isEmpty(obj.shareId)), {
|
||||
message: "one of id or shareId is required",
|
||||
});
|
||||
|
||||
export type DocumentsInfoReq = z.infer<typeof DocumentsInfoSchema>;
|
||||
|
||||
export const DocumentsExportSchema = z
|
||||
.object({
|
||||
/** Id of the document to be exported */
|
||||
id: z.string().uuid().optional(),
|
||||
|
||||
/** Share Id, if available */
|
||||
shareId: z.string().uuid().optional(),
|
||||
})
|
||||
.refine((obj) => !(isEmpty(obj.id) && isEmpty(obj.shareId)), {
|
||||
message: "one of id or shareId is required",
|
||||
});
|
||||
|
||||
export type DocumentsExportReq = z.infer<typeof DocumentsExportSchema>;
|
||||
|
||||
export const DocumentsRestoreSchema = BaseIdSchema.extend({
|
||||
/** Id of the collection to which the document belongs */
|
||||
collectionId: z.string().uuid().optional(),
|
||||
|
||||
/** Id of document revision */
|
||||
revisionId: z.string().uuid().optional(),
|
||||
});
|
||||
|
||||
export type DocumentsRestoreReq = z.infer<typeof DocumentsRestoreSchema>;
|
||||
|
||||
export const DocumentsSearchTitlesSchema = SearchQuerySchema.extend({});
|
||||
|
||||
export type DocumentsSearchTitlesReq = z.infer<
|
||||
typeof DocumentsSearchTitlesSchema
|
||||
>;
|
||||
|
||||
export const DocumentsSearchSchema = SearchQuerySchema.merge(
|
||||
DateFilterSchema
|
||||
).extend({
|
||||
/** Whether to include archived docs in results */
|
||||
includeArchived: z.boolean().optional(),
|
||||
|
||||
/** Whether to include drafts in results */
|
||||
includeDrafts: z.boolean().optional(),
|
||||
|
||||
/** Filter results for team based on the collection */
|
||||
collectionId: z.string().uuid().optional(),
|
||||
|
||||
/** Filter results based on user */
|
||||
userId: z.string().uuid().optional(),
|
||||
|
||||
/** Filter results for the team derived from shareId */
|
||||
shareId: z.string().uuid().optional(),
|
||||
|
||||
/** Min words to be shown in the results snippets */
|
||||
snippetMinWords: z.number().default(20),
|
||||
|
||||
/** Max words to be accomodated in the results snippets */
|
||||
snippetMaxWords: z.number().default(30),
|
||||
});
|
||||
|
||||
export type DocumentsSearchReq = z.infer<typeof DocumentsSearchSchema>;
|
||||
|
||||
export const DocumentsTemplatizeSchema = BaseIdSchema.extend({});
|
||||
|
||||
export type DocumentsTemplatizeReq = z.infer<typeof DocumentsTemplatizeSchema>;
|
||||
|
||||
export const DocumentsUpdateSchema = BaseIdSchema.extend({
|
||||
/** Doc title to be updated */
|
||||
title: z.string().optional(),
|
||||
|
||||
/** Doc text to be updated */
|
||||
text: z.string().optional(),
|
||||
|
||||
/** Boolean to denote if the doc should occupy full width */
|
||||
fullWidth: z.boolean().optional(),
|
||||
|
||||
/** Boolean to denote if the doc should be published */
|
||||
publish: z.boolean().optional(),
|
||||
|
||||
/** Revision to compare against document revision count */
|
||||
lastRevision: z.number().optional(),
|
||||
|
||||
/** Doc template Id */
|
||||
templateId: z.string().uuid().nullish(),
|
||||
|
||||
/** Doc collection Id */
|
||||
collectionId: z.string().uuid().optional(),
|
||||
|
||||
/** Boolean to denote if text should be appended */
|
||||
append: z.boolean().optional(),
|
||||
}).refine((obj) => !(obj.append && !obj.text), {
|
||||
message: "text is required while appending",
|
||||
});
|
||||
|
||||
export type DocumentsUpdateReq = z.infer<typeof DocumentsUpdateSchema>;
|
||||
|
||||
export const DocumentsMoveSchema = BaseIdSchema.extend({
|
||||
/** Id of collection to which the doc is supposed to be moved */
|
||||
collectionId: z.string().uuid(),
|
||||
|
||||
/** Parent Id, in case if the doc is moved to a new parent */
|
||||
parentDocumentId: z.string().uuid().optional(),
|
||||
|
||||
/** Helps evaluate the new index in collection structure upon move */
|
||||
index: z.number().positive().optional(),
|
||||
}).refine((obj) => !(obj.parentDocumentId === obj.id), {
|
||||
message: "infinite loop detected, cannot nest a document inside itself",
|
||||
});
|
||||
|
||||
export type DocumentsMoveReq = z.infer<typeof DocumentsMoveSchema>;
|
||||
|
||||
export const DocumentsArchiveSchema = BaseIdSchema.extend({});
|
||||
|
||||
export type DocumentsArchiveReq = z.infer<typeof DocumentsArchiveSchema>;
|
||||
|
||||
export const DocumentsDeleteSchema = BaseIdSchema.extend({
|
||||
/** Whether to permanently delete the doc as opposed to soft-delete */
|
||||
permanent: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type DocumentsDeleteReq = z.infer<typeof DocumentsDeleteSchema>;
|
||||
|
||||
export const DocumentsUnpublishSchema = BaseIdSchema.extend({});
|
||||
|
||||
export type DocumentsUnpublishReq = z.infer<typeof DocumentsUnpublishSchema>;
|
||||
|
||||
export const DocumentsImportSchema = z.object({
|
||||
/** Whether to publish the imported docs */
|
||||
publish: z.boolean().optional(),
|
||||
|
||||
/** Import docs to this collection */
|
||||
collectionId: z.string().uuid(),
|
||||
|
||||
/** Import under this parent doc */
|
||||
parentDocumentId: z.string().uuid().optional(),
|
||||
});
|
||||
|
||||
export type DocumentsImportReq = z.infer<typeof DocumentsImportSchema>;
|
||||
|
||||
export const DocumentsCreateSchema = z
|
||||
.object({
|
||||
/** Doc title */
|
||||
title: z.string().default(""),
|
||||
|
||||
/** Doc text */
|
||||
text: z.string().default(""),
|
||||
|
||||
/** Boolean to denote if the doc should be published */
|
||||
publish: z.boolean().optional(),
|
||||
|
||||
/** Create Doc under this collection */
|
||||
collectionId: z.string().uuid().optional(),
|
||||
|
||||
/** Create Doc under this parent */
|
||||
parentDocumentId: z.string().uuid().optional(),
|
||||
|
||||
/** Create doc with this template */
|
||||
templateId: z.string().uuid().optional(),
|
||||
|
||||
/** Whether to create a template doc */
|
||||
template: z.boolean().optional(),
|
||||
})
|
||||
.refine((obj) => !(obj.parentDocumentId && !obj.collectionId), {
|
||||
message: "collectionId is required to create a nested document",
|
||||
})
|
||||
.refine((obj) => !(obj.template && !obj.collectionId), {
|
||||
message: "collectionId is required to create a template document",
|
||||
})
|
||||
.refine((obj) => !(obj.publish && !obj.collectionId), {
|
||||
message: "collectionId is required to publish",
|
||||
});
|
||||
|
||||
export type DocumentsCreateReq = z.infer<typeof DocumentsCreateSchema>;
|
||||
Reference in New Issue
Block a user