From ba388dc382958c698b07181d330a026539e9a549 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 2 Mar 2026 13:32:31 -0600 Subject: [PATCH] feat: create project workspace alongside project in single request Allow passing an optional workspace object when creating a project, creating both the project and its workspace in one API call. Co-Authored-By: Claude Opus 4.6 --- server/src/routes/projects.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/server/src/routes/projects.ts b/server/src/routes/projects.ts index 7cbecaa1..1785b1dd 100644 --- a/server/src/routes/projects.ts +++ b/server/src/routes/projects.ts @@ -35,7 +35,22 @@ export function projectRoutes(db: Db) { router.post("/companies/:companyId/projects", validate(createProjectSchema), async (req, res) => { const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); - const project = await svc.create(companyId, req.body); + const { workspace, ...projectData } = req.body as Record & { + workspace?: Record; + }; + const project = await svc.create(companyId, projectData); + let createdWorkspaceId: string | null = null; + if (workspace) { + const createdWorkspace = await svc.createWorkspace(project.id, workspace); + if (!createdWorkspace) { + await svc.remove(project.id); + res.status(422).json({ error: "Invalid project workspace payload" }); + return; + } + createdWorkspaceId = createdWorkspace.id; + } + const hydratedProject = workspace ? await svc.getById(project.id) : project; + const actor = getActorInfo(req); await logActivity(db, { companyId, @@ -45,9 +60,12 @@ export function projectRoutes(db: Db) { action: "project.created", entityType: "project", entityId: project.id, - details: { name: project.name }, + details: { + name: project.name, + workspaceId: createdWorkspaceId, + }, }); - res.status(201).json(project); + res.status(201).json(hydratedProject ?? project); }); router.patch("/projects/:id", validate(updateProjectSchema), async (req, res) => {