From ae4bc774074094f4e49ab55ef8b421735debe8c8 Mon Sep 17 00:00:00 2001 From: Morfixx <63129378+nahSystemu@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:29:35 -0700 Subject: [PATCH] feat: added schemas for openapi (#452) --- .../views/board/components/NewCardForm.tsx | 3 + packages/api/src/routers/attachment.ts | 3 +- packages/api/src/routers/board.ts | 19 +- packages/api/src/routers/card.ts | 50 ++-- packages/api/src/routers/list.ts | 10 +- packages/api/src/routers/member.ts | 3 +- packages/api/src/routers/workspace.ts | 42 ++-- packages/api/src/schemas/attachment.ts | 12 + packages/api/src/schemas/board.ts | 130 +++++++++++ packages/api/src/schemas/card.ts | 213 ++++++++++++++++++ packages/api/src/schemas/common.ts | 39 ++++ packages/api/src/schemas/index.ts | 39 ++++ packages/api/src/schemas/list.ts | 13 ++ packages/api/src/schemas/member.ts | 6 + packages/api/src/schemas/workspace.ts | 92 ++++++++ 15 files changed, 606 insertions(+), 68 deletions(-) create mode 100644 packages/api/src/schemas/attachment.ts create mode 100644 packages/api/src/schemas/board.ts create mode 100644 packages/api/src/schemas/card.ts create mode 100644 packages/api/src/schemas/common.ts create mode 100644 packages/api/src/schemas/index.ts create mode 100644 packages/api/src/schemas/list.ts create mode 100644 packages/api/src/schemas/member.ts create mode 100644 packages/api/src/schemas/workspace.ts diff --git a/apps/web/src/views/board/components/NewCardForm.tsx b/apps/web/src/views/board/components/NewCardForm.tsx index 622c6d4c..d242a921 100644 --- a/apps/web/src/views/board/components/NewCardForm.tsx +++ b/apps/web/src/views/board/components/NewCardForm.tsx @@ -158,6 +158,9 @@ export function NewCardForm({ ...member, deletedAt: null, })) ?? [], + comments: [], + checklists: [], + attachments: [], _filteredLabels: labelPublicIds.map((id) => ({ publicId: id })), _filteredMembers: memberPublicIds.map((id) => ({ publicId: id })), index: position === "start" ? 0 : list.cards.length, diff --git a/packages/api/src/routers/attachment.ts b/packages/api/src/routers/attachment.ts index 6434a76e..b696d454 100644 --- a/packages/api/src/routers/attachment.ts +++ b/packages/api/src/routers/attachment.ts @@ -8,6 +8,7 @@ import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { generateUID } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { attachmentConfirmResponseSchema } from "../schemas"; import { assertPermission } from "../utils/permissions"; import { deleteObject, generateUploadUrl } from "@kan/shared/utils"; @@ -110,7 +111,7 @@ export const attachmentRouter = createTRPCRouter({ size: z.number().positive(), }), ) - .output(z.custom>>()) + .output(attachmentConfirmResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; diff --git a/packages/api/src/routers/board.ts b/packages/api/src/routers/board.ts index ef764fd9..42bf817a 100644 --- a/packages/api/src/routers/board.ts +++ b/packages/api/src/routers/board.ts @@ -16,6 +16,13 @@ import { } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; +import { + boardListItemSchema, + boardDetailSchema, + boardBySlugSchema, + boardCreateResponseSchema, + boardUpdateResponseSchema, +} from "../schemas"; import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions"; export const boardRouter = createTRPCRouter({ @@ -37,9 +44,7 @@ export const boardRouter = createTRPCRouter({ archived: z.boolean().optional(), }), ) - .output( - z.custom>>(), - ) + .output(z.array(boardListItemSchema)) .query(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -106,7 +111,7 @@ export const boardRouter = createTRPCRouter({ type: z.enum(["regular", "template"]).optional(), }), ) - .output(z.custom>>()) + .output(boardDetailSchema) .query(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -246,7 +251,7 @@ export const boardRouter = createTRPCRouter({ .optional(), }), ) - .output(z.custom>>()) + .output(boardBySlugSchema.nullable()) .query(async ({ ctx, input }) => { const workspace = await workspaceRepo.getBySlugWithBoards( ctx.db, @@ -299,7 +304,7 @@ export const boardRouter = createTRPCRouter({ sourceBoardPublicId: z.string().min(12).optional(), }), ) - .output(z.custom>>()) + .output(boardCreateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -469,7 +474,7 @@ export const boardRouter = createTRPCRouter({ isArchived: z.boolean().optional(), }), ) - .output(z.object({ success: z.boolean() }).or(z.custom>>())) + .output(boardUpdateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; diff --git a/packages/api/src/routers/card.ts b/packages/api/src/routers/card.ts index 66550496..5591120f 100644 --- a/packages/api/src/routers/card.ts +++ b/packages/api/src/routers/card.ts @@ -10,6 +10,14 @@ import * as listRepo from "@kan/db/repository/list.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; +import { + cardCreateResponseSchema, + cardUpdateResponseSchema, + cardDetailSchema, + commentResponseSchema, + commentDeleteResponseSchema, + activityItemSchema, +} from "../schemas"; import { mergeActivities } from "../utils/activities"; import { sendMentionEmails } from "../utils/notifications"; import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions"; @@ -42,7 +50,7 @@ export const cardRouter = createTRPCRouter({ dueDate: z.date().nullable().optional(), }), ) - .output(z.custom>>()) + .output(cardCreateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -215,7 +223,7 @@ export const cardRouter = createTRPCRouter({ comment: z.string().min(1), }), ) - .output(z.custom>>()) + .output(commentResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -288,7 +296,7 @@ export const cardRouter = createTRPCRouter({ comment: z.string().min(1), }), ) - .output(z.custom>>()) + .output(commentResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -376,7 +384,7 @@ export const cardRouter = createTRPCRouter({ commentPublicId: z.string().min(12), }), ) - .output(z.custom>>()) + .output(commentDeleteResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -435,7 +443,7 @@ export const cardRouter = createTRPCRouter({ createdBy: userId, }); - return deletedComment; + return { publicId: input.commentPublicId }; }), addOrRemoveLabel: protectedProcedure .meta({ @@ -639,25 +647,7 @@ export const cardRouter = createTRPCRouter({ }, }) .input(z.object({ cardPublicId: z.string().min(12) })) - .output( - z.custom< - Omit< - NonNullable< - Awaited> - >, - "attachments" - > & { - attachments: { - publicId: string; - contentType: string; - s3Key: string; - originalFilename: string | null; - size?: number | null; - url: string | null; - }[]; - } - >(), - ) + .output(cardDetailSchema) .query(async ({ ctx, input }) => { const card = await cardRepo.getWorkspaceAndCardIdByCardPublicId( ctx.db, @@ -763,15 +753,7 @@ export const cardRouter = createTRPCRouter({ ) .output( z.object({ - activities: z.array( - z.custom< - NonNullable< - Awaited< - ReturnType - > - >["activities"][number] - >(), - ), + activities: z.array(activityItemSchema), hasMore: z.boolean(), nextCursor: z.string().datetime().nullable(), }), @@ -871,7 +853,7 @@ export const cardRouter = createTRPCRouter({ dueDate: z.date().nullable().optional(), }), ) - .output(z.custom>>()) + .output(cardUpdateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; diff --git a/packages/api/src/routers/list.ts b/packages/api/src/routers/list.ts index e0085b2b..7565f6b1 100644 --- a/packages/api/src/routers/list.ts +++ b/packages/api/src/routers/list.ts @@ -7,6 +7,7 @@ import * as activityRepo from "@kan/db/repository/cardActivity.repo"; import * as listRepo from "@kan/db/repository/list.repo"; import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { listCreateResponseSchema, listUpdateResponseSchema } from "../schemas"; import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions"; export const listRouter = createTRPCRouter({ @@ -27,7 +28,7 @@ export const listRouter = createTRPCRouter({ boardPublicId: z.string().min(12), }), ) - .output(z.custom>>()) + .output(listCreateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -163,12 +164,7 @@ export const listRouter = createTRPCRouter({ index: z.number().optional(), }), ) - .output( - z.custom< - | Awaited> - | Awaited> - >(), - ) + .output(listUpdateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; diff --git a/packages/api/src/routers/member.ts b/packages/api/src/routers/member.ts index b5b55d30..6e518b94 100644 --- a/packages/api/src/routers/member.ts +++ b/packages/api/src/routers/member.ts @@ -16,6 +16,7 @@ import { import { updateSubscriptionSeats } from "@kan/stripe"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; +import { memberInviteResponseSchema } from "../schemas"; import { assertCanManageMember, assertCanManageRole, @@ -40,7 +41,7 @@ export const memberRouter = createTRPCRouter({ workspacePublicId: z.string().min(12), }), ) - .output(z.custom>>()) + .output(memberInviteResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; diff --git a/packages/api/src/routers/workspace.ts b/packages/api/src/routers/workspace.ts index 138830f5..acf1f404 100644 --- a/packages/api/src/routers/workspace.ts +++ b/packages/api/src/routers/workspace.ts @@ -7,6 +7,14 @@ import * as workspaceSlugRepo from "@kan/db/repository/workspaceSlug.repo"; import { generateAvatarUrl, generateUID } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; +import { + workspaceListItemSchema, + workspaceDetailSchema, + workspaceWithBoardsSchema, + workspaceCreateResponseSchema, + workspaceUpdateResponseSchema, + workspaceDeleteResponseSchema, +} from "../schemas"; import { assertPermission } from "../utils/permissions"; export const workspaceRouter = createTRPCRouter({ @@ -22,9 +30,7 @@ export const workspaceRouter = createTRPCRouter({ }, }) .input(z.void()) - .output( - z.custom>>(), - ) + .output(z.array(workspaceListItemSchema)) .query(async ({ ctx }) => { const userId = ctx.user?.id; @@ -50,11 +56,7 @@ export const workspaceRouter = createTRPCRouter({ }, }) .input(z.object({ workspacePublicId: z.string().min(12) })) - .output( - z.custom< - Awaited> - >(), - ) + .output(workspaceDetailSchema) .query(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -134,7 +136,7 @@ export const workspaceRouter = createTRPCRouter({ return { ...result, members: sanitizedMembers, - } as Awaited>; + }; } return { @@ -162,9 +164,7 @@ export const workspaceRouter = createTRPCRouter({ .regex(/^(?![-]+$)[a-zA-Z0-9-]+$/), }), ) - .output( - z.custom>>(), - ) + .output(workspaceWithBoardsSchema) .query(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -211,7 +211,7 @@ export const workspaceRouter = createTRPCRouter({ .optional(), }), ) - .output(z.custom>>()) + .output(workspaceCreateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; const userEmail = ctx.user?.email; @@ -270,7 +270,13 @@ export const workspaceRouter = createTRPCRouter({ code: "INTERNAL_SERVER_ERROR", }); - return result; + return { + publicId: result.publicId, + name: result.name!, + slug: result.slug!, + description: result.description ?? null, + plan: result.plan!, + }; }), update: protectedProcedure .meta({ @@ -300,7 +306,7 @@ export const workspaceRouter = createTRPCRouter({ .optional(), }), ) - .output(z.custom>>()) + .output(workspaceUpdateResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -383,7 +389,7 @@ export const workspaceRouter = createTRPCRouter({ }, }) .input(z.object({ workspacePublicId: z.string().min(12) })) - .output(z.custom>>()) + .output(workspaceDeleteResponseSchema) .mutation(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -405,12 +411,12 @@ export const workspaceRouter = createTRPCRouter({ }); await assertPermission(ctx.db, userId, workspace.id, "workspace:delete"); - const result = await workspaceRepo.hardDelete( + await workspaceRepo.hardDelete( ctx.db, input.workspacePublicId, ); - return result; + return { success: true }; }), checkSlugAvailability: publicProcedure .meta({ diff --git a/packages/api/src/schemas/attachment.ts b/packages/api/src/schemas/attachment.ts new file mode 100644 index 00000000..6ca9f3e5 --- /dev/null +++ b/packages/api/src/schemas/attachment.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +// ─── attachment.confirm ────────────────────────────────────── +export const attachmentConfirmResponseSchema = z.object({ + publicId: z.string(), + filename: z.string(), + originalFilename: z.string(), + contentType: z.string(), + size: z.number(), + s3Key: z.string(), + createdAt: z.date(), +}); diff --git a/packages/api/src/schemas/board.ts b/packages/api/src/schemas/board.ts new file mode 100644 index 00000000..5327087e --- /dev/null +++ b/packages/api/src/schemas/board.ts @@ -0,0 +1,130 @@ +import { z } from "zod"; + +import { + checklistResponseSchema, + labelSchema, + workspaceMemberSchema, +} from "./common"; + +// ─── board.all ─────────────────────────────────────────────── +export const boardListItemSchema = z.object({ + publicId: z.string(), + name: z.string(), + favorite: z.boolean(), + lists: z.array( + z.object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + }), + ), + labels: z.array(labelSchema), +}); + +// ─── Card sub-object inside board detail (byId) ───────────── +const boardCardMemberSchema = z.object({ + publicId: z.string(), + email: z.string(), + user: z + .object({ + name: z.string().nullable(), + email: z.string(), + image: z.string().nullable(), + }) + .nullable(), +}); + +const boardDetailCardSchema = z.object({ + publicId: z.string(), + title: z.string(), + description: z.string().nullable(), + index: z.number(), + dueDate: z.date().nullable(), + labels: z.array(labelSchema), + members: z.array(boardCardMemberSchema), + attachments: z.array(z.object({ publicId: z.string() })), + checklists: z.array(checklistResponseSchema), + comments: z.array(z.object({ publicId: z.string() })), +}); + +// ─── board.byId ────────────────────────────────────────────── +export const boardDetailSchema = z.object({ + publicId: z.string(), + name: z.string(), + slug: z.string(), + visibility: z.string(), + isArchived: z.boolean(), + favorite: z.boolean(), + workspace: z.object({ + publicId: z.string(), + members: z.array(workspaceMemberSchema), + }), + labels: z.array(labelSchema), + lists: z.array( + z.object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + cards: z.array(boardDetailCardSchema), + }), + ), + allLists: z.array( + z.object({ + publicId: z.string(), + name: z.string(), + }), + ), +}); + +// ─── Card sub-object inside board detail (bySlug — no members) ─ +const boardSlugCardSchema = z.object({ + publicId: z.string(), + title: z.string(), + description: z.string().nullable(), + index: z.number(), + dueDate: z.date().nullable(), + labels: z.array(labelSchema), + attachments: z.array(z.object({ publicId: z.string() })), + checklists: z.array(checklistResponseSchema), + comments: z.array(z.object({ publicId: z.string() })), +}); + +// ─── board.bySlug ──────────────────────────────────────────── +export const boardBySlugSchema = z.object({ + publicId: z.string(), + name: z.string(), + slug: z.string(), + visibility: z.string(), + workspace: z.object({ + publicId: z.string(), + name: z.string(), + slug: z.string(), + }), + labels: z.array(labelSchema), + lists: z.array( + z.object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + cards: z.array(boardSlugCardSchema), + }), + ), + allLists: z.array( + z.object({ + publicId: z.string(), + name: z.string(), + }), + ), +}); + +// ─── board.create / board.createFromSnapshot ───────────────── +export const boardCreateResponseSchema = z.object({ + publicId: z.string(), + name: z.string(), +}); + +// ─── board.update ──────────────────────────────────────────── +export const boardUpdateResponseSchema = z.union([ + z.object({ success: z.boolean() }), + z.object({ publicId: z.string(), name: z.string() }), +]); diff --git a/packages/api/src/schemas/card.ts b/packages/api/src/schemas/card.ts new file mode 100644 index 00000000..8ffac68e --- /dev/null +++ b/packages/api/src/schemas/card.ts @@ -0,0 +1,213 @@ +import { z } from "zod"; + +import { + checklistResponseSchema, + labelSchema, + workspaceMemberSchema, +} from "./common"; + +// ─── card.create ───────────────────────────────────────────── +export const cardCreateResponseSchema = z.object({ + publicId: z.string(), +}); + +// ─── card.update ───────────────────────────────────────────── +export const cardUpdateResponseSchema = z.object({ + publicId: z.string(), + title: z.string(), + description: z.string().nullable(), + dueDate: z.date().nullable(), +}); + +// ─── Comment responses ─────────────────────────────────────── +export const commentResponseSchema = z.object({ + publicId: z.string(), + comment: z.string(), +}); + +export const commentDeleteResponseSchema = z.object({ + publicId: z.string(), +}); + +// ─── card.byId ─────────────────────────────────────────────── + +const cardMemberSchema = z.object({ + publicId: z.string(), + email: z.string(), + user: z + .object({ + name: z.string().nullable(), + }) + .nullable(), +}); + +export const cardDetailSchema = z.object({ + publicId: z.string(), + title: z.string(), + description: z.string().nullable(), + dueDate: z.date().nullable(), + createdBy: z.string().nullable(), + labels: z.array(labelSchema), + attachments: z.array( + z.object({ + publicId: z.string(), + contentType: z.string(), + s3Key: z.string(), + originalFilename: z.string().nullable(), + size: z.number().nullable(), + url: z.string().nullable(), + }), + ), + checklists: z.array(checklistResponseSchema), + list: z.object({ + publicId: z.string(), + name: z.string(), + board: z.object({ + publicId: z.string(), + name: z.string(), + labels: z.array(labelSchema), + lists: z.array( + z.object({ + publicId: z.string(), + name: z.string(), + }), + ), + workspace: z.object({ + publicId: z.string(), + members: z.array(workspaceMemberSchema), + }), + }), + }), + members: z.array(cardMemberSchema), + activities: z.array( + z.object({ + publicId: z.string(), + type: z.string(), + createdAt: z.date(), + fromIndex: z.number().nullable(), + toIndex: z.number().nullable(), + fromTitle: z.string().nullable(), + toTitle: z.string().nullable(), + fromDescription: z.string().nullable(), + toDescription: z.string().nullable(), + fromDueDate: z.date().nullable(), + toDueDate: z.date().nullable(), + fromList: z + .object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + }) + .nullable(), + toList: z + .object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + }) + .nullable(), + label: z + .object({ + publicId: z.string(), + name: z.string(), + }) + .nullable(), + member: z + .object({ + publicId: z.string(), + user: z + .object({ + name: z.string().nullable(), + email: z.string(), + }) + .nullable(), + }) + .nullable(), + user: z + .object({ + name: z.string().nullable(), + email: z.string(), + }) + .nullable(), + comment: z + .object({ + publicId: z.string(), + comment: z.string(), + createdBy: z.string().nullable(), + updatedAt: z.date().nullable(), + deletedAt: z.date().nullable(), + }) + .nullable(), + }), + ), +}); + +// ─── card.getActivities ────────────────────────────────────── +export const activityItemSchema = z.object({ + publicId: z.string(), + type: z.string(), + createdAt: z.date(), + fromIndex: z.number().nullable(), + toIndex: z.number().nullable(), + fromTitle: z.string().nullable(), + toTitle: z.string().nullable(), + fromDescription: z.string().nullable(), + toDescription: z.string().nullable(), + fromDueDate: z.date().nullable(), + toDueDate: z.date().nullable(), + fromList: z + .object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + }) + .nullable(), + toList: z + .object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + }) + .nullable(), + label: z + .object({ + publicId: z.string(), + name: z.string(), + }) + .nullable(), + member: z + .object({ + publicId: z.string(), + user: z + .object({ + name: z.string().nullable(), + email: z.string(), + image: z.string().nullable(), + }) + .nullable(), + }) + .nullable(), + user: z + .object({ + name: z.string().nullable(), + email: z.string(), + image: z.string().nullable(), + }) + .nullable(), + comment: z + .object({ + publicId: z.string(), + comment: z.string(), + createdBy: z.string().nullable(), + updatedAt: z.date().nullable(), + deletedAt: z.date().nullable(), + }) + .nullable(), + attachment: z + .object({ + publicId: z.string(), + filename: z.string(), + originalFilename: z.string().nullable(), + }) + .nullable(), +}); diff --git a/packages/api/src/schemas/common.ts b/packages/api/src/schemas/common.ts new file mode 100644 index 00000000..86c308a0 --- /dev/null +++ b/packages/api/src/schemas/common.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; + +// Shared label schema used across board and card responses +export const labelSchema = z.object({ + publicId: z.string(), + name: z.string(), + colourCode: z.string().nullable(), +}); + +// Shared checklist item schema +export const checklistItemResponseSchema = z.object({ + publicId: z.string(), + title: z.string(), + completed: z.boolean(), + index: z.number(), +}); + +// Shared checklist schema with items +export const checklistResponseSchema = z.object({ + publicId: z.string(), + name: z.string(), + index: z.number(), + items: z.array(checklistItemResponseSchema), +}); + +// Shared user sub-object (used inside member/workspace responses) +export const userSchema = z.object({ + name: z.string().nullable(), + email: z.string(), + image: z.string().nullable(), +}); + +// Workspace member sub-object +export const workspaceMemberSchema = z.object({ + publicId: z.string(), + email: z.string(), + status: z.string(), + user: userSchema.nullable(), +}); diff --git a/packages/api/src/schemas/index.ts b/packages/api/src/schemas/index.ts new file mode 100644 index 00000000..74110b5a --- /dev/null +++ b/packages/api/src/schemas/index.ts @@ -0,0 +1,39 @@ +export { + boardListItemSchema, + boardDetailSchema, + boardBySlugSchema, + boardCreateResponseSchema, + boardUpdateResponseSchema, +} from "./board"; + +export { + cardCreateResponseSchema, + cardUpdateResponseSchema, + cardDetailSchema, + commentResponseSchema, + commentDeleteResponseSchema, + activityItemSchema, +} from "./card"; + +export { + labelSchema, + checklistItemResponseSchema, + checklistResponseSchema, + userSchema, + workspaceMemberSchema, +} from "./common"; + +export { + workspaceListItemSchema, + workspaceDetailSchema, + workspaceWithBoardsSchema, + workspaceCreateResponseSchema, + workspaceUpdateResponseSchema, + workspaceDeleteResponseSchema, +} from "./workspace"; + +export { listCreateResponseSchema, listUpdateResponseSchema } from "./list"; + +export { memberInviteResponseSchema } from "./member"; + +export { attachmentConfirmResponseSchema } from "./attachment"; diff --git a/packages/api/src/schemas/list.ts b/packages/api/src/schemas/list.ts new file mode 100644 index 00000000..378375b6 --- /dev/null +++ b/packages/api/src/schemas/list.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +// ─── list.create ───────────────────────────────────────────── +export const listCreateResponseSchema = z.object({ + publicId: z.string(), + name: z.string(), +}); + +// ─── list.update / list.reorder ────────────────────────────── +export const listUpdateResponseSchema = z.object({ + publicId: z.string(), + name: z.string(), +}); diff --git a/packages/api/src/schemas/member.ts b/packages/api/src/schemas/member.ts new file mode 100644 index 00000000..3719dffd --- /dev/null +++ b/packages/api/src/schemas/member.ts @@ -0,0 +1,6 @@ +import { z } from "zod"; + +// ─── member.invite ─────────────────────────────────────────── +export const memberInviteResponseSchema = z.object({ + publicId: z.string(), +}); diff --git a/packages/api/src/schemas/workspace.ts b/packages/api/src/schemas/workspace.ts new file mode 100644 index 00000000..e57c02e0 --- /dev/null +++ b/packages/api/src/schemas/workspace.ts @@ -0,0 +1,92 @@ +import { z } from "zod"; + +import { userSchema } from "./common"; + +// ─── workspace.all ─────────────────────────────────────────── +export const workspaceListItemSchema = z.object({ + role: z.string(), + workspace: z.object({ + publicId: z.string(), + name: z.string(), + description: z.string().nullable(), + slug: z.string(), + plan: z.enum(["free", "pro", "enterprise"]), + weekStartDay: z.number().nullable(), + deletedAt: z.date().nullable(), + }), +}); + +// ─── workspace.byId ────────────────────────────────────────── +const workspaceMemberDetailSchema = z.object({ + publicId: z.string(), + email: z.string().optional(), + role: z.string(), + status: z.string(), + createdAt: z.date(), + user: z + .object({ + name: z.string().nullable(), + email: z.string().optional(), + image: z.string().nullable(), + }) + .nullable(), +}); + +const workspaceSubscriptionSchema = z.object({ + plan: z.string(), + status: z.string(), + seats: z.number().nullable(), + unlimitedSeats: z.boolean().nullable(), + periodStart: z.date().nullable(), + periodEnd: z.date().nullable(), +}); + +export const workspaceDetailSchema = z.object({ + publicId: z.string(), + name: z.string(), + slug: z.string(), + showEmailsToMembers: z.boolean().nullable(), + weekStartDay: z.number().nullable(), + members: z.array(workspaceMemberDetailSchema), + subscriptions: z.array(workspaceSubscriptionSchema), +}); + +// ─── workspace.bySlug ──────────────────────────────────────── +export const workspaceWithBoardsSchema = z.object({ + publicId: z.string(), + name: z.string(), + description: z.string().nullable(), + slug: z.string(), + boards: z.array( + z.object({ + publicId: z.string(), + slug: z.string(), + name: z.string(), + }), + ), +}); + +// ─── workspace.create ──────────────────────────────────────── +export const workspaceCreateResponseSchema = z.object({ + publicId: z.string(), + name: z.string(), + slug: z.string(), + description: z.string().nullable(), + plan: z.enum(["free", "pro", "enterprise"]), +}); + +// ─── workspace.update ──────────────────────────────────────── +export const workspaceUpdateResponseSchema = z.object({ + publicId: z.string(), + name: z.string(), + slug: z.string(), + description: z.string().nullable(), + plan: z.enum(["free", "pro", "enterprise"]), + showEmailsToMembers: z.boolean().nullable(), + weekStartDay: z.number().nullable(), +}); + +// ─── workspace.delete ──────────────────────────────────────── +export const workspaceDeleteResponseSchema = z.object({ + success: z.boolean(), +});