import { Inject, Body, Controller, Delete, Get, Param, Patch, Post, Query, Req } from "@nestjs/common"; import type { Request } from "express"; import { z } from "zod"; import { WorkspacesService } from "./workspaces.service.js"; import { DatabaseService } from "../databases/database.service.js"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { requireActor, requireUser } from "../../common/types.js"; import { ApiError } from "../../common/errors.js"; import { CreateWorkspaceDto, type CreateWorkspaceInput, InviteMemberDto, type InviteMemberInput, ListMembersQuery, type ListMembersInput, ListWorkspacesQuery, type ListWorkspacesInput, TransferWorkspaceDto, type TransferWorkspaceInput, UpdateMemberRoleDto, type UpdateMemberRoleInput, UpdateWorkspaceDto, type UpdateWorkspaceInput } from "./dto.js"; const DatabasesListQuery = z.object({ limit: z.coerce.number().int().min(1).max(100).optional() }); @Controller("workspaces") export class WorkspacesController { constructor( @Inject(WorkspacesService) private readonly workspaces: WorkspacesService, @Inject(DatabaseService) private readonly databases: DatabaseService ) {} // T-136: optional directory filters/sort (no params = previous behavior). @Get() list( @Query(ZodValidate(ListWorkspacesQuery)) query: ListWorkspacesInput, @Req() req: Request ) { return this.workspaces.listMine(requireUser(req), query); } @Post() create( @Body(ZodValidate(CreateWorkspaceDto)) body: CreateWorkspaceInput, @Req() req: Request ) { return this.workspaces.create(requireUser(req), body); } // TASK-0127: workspace-scope routes accept API keys (requireActor) — the // key acts as its own workspace. USER-IDENTITY-only routes (listMine, // create, acceptInvite, transfer — the caller's OWN identity is the // subject) keep requireUser. @Get(":id") get(@Param("id") id: string, @Req() req: Request) { return this.workspaces.get(id, requireActor(req)); } @Patch(":id") update( @Param("id") id: string, @Body(ZodValidate(UpdateWorkspaceDto)) body: UpdateWorkspaceInput, @Req() req: Request ) { return this.workspaces.update(id, requireActor(req), body, req); } @Delete(":id") remove(@Param("id") id: string, @Req() req: Request) { return this.workspaces.remove(id, requireUser(req), req).then(() => ({ deleted: true })); } // ---------- members ---------- @Get(":id/members") listMembers( @Param("id") id: string, @Query(ZodValidate(ListMembersQuery)) query: ListMembersInput, @Req() req: Request ) { return this.workspaces.listMembers(id, requireActor(req), query); } @Post(":id/members/invites") invite( @Param("id") id: string, @Body(ZodValidate(InviteMemberDto)) body: InviteMemberInput, @Req() req: Request ) { return this.workspaces.invite(id, requireUser(req), body, req); } @Post(":id/members/invites/:inviteId/accept") acceptInvite( @Param("id") id: string, @Param("inviteId") _inviteId: string, @Query("token") token: string | undefined, @Req() req: Request ) { if (!token) { throw ApiError.validation("Invitation token required (?token=…).", [{ field: "token", issue: "required" }]); } return this.workspaces.acceptInvite(id, requireUser(req), token, req); } @Patch(":id/members/:userId") updateMemberRole( @Param("id") id: string, @Param("userId") userId: string, @Body(ZodValidate(UpdateMemberRoleDto)) body: UpdateMemberRoleInput, @Req() req: Request ) { return this.workspaces.updateMemberRole(id, requireUser(req), userId, body.role, req); } @Delete(":id/members/:userId") removeMember( @Param("id") id: string, @Param("userId") userId: string, @Req() req: Request ) { return this.workspaces.removeMember(id, requireUser(req), userId, req).then(() => ({ removed: true })); } // ---------- usage & transfer ---------- @Get(":id/databases") listDatabases( @Param("id") id: string, @Query(ZodValidate(DatabasesListQuery)) query: z.infer, @Req() req: Request ) { return this.databases.listByWorkspace(id, requireActor(req), query.limit ?? 50); } @Get(":id/usage") usage(@Param("id") id: string, @Req() req: Request) { return this.workspaces.usage(id, requireActor(req)); } @Post(":id/transfer") transfer( @Param("id") id: string, @Body(ZodValidate(TransferWorkspaceDto)) body: TransferWorkspaceInput, @Req() req: Request ) { return this.workspaces.transfer(id, requireUser(req), body.newOwnerId, req); } // ---------- T-136: archive / restore / stats ---------- @Post(":id/archive") archive(@Param("id") id: string, @Req() req: Request) { return this.workspaces.archive(id, requireUser(req), req); } @Post(":id/archive/restore") restore(@Param("id") id: string, @Req() req: Request) { return this.workspaces.restoreWorkspace(id, requireUser(req), req); } @Get(":id/stats") stats(@Param("id") id: string, @Req() req: Request) { return this.workspaces.stats(id, requireUser(req)); } // ---------- T-158: space activity feed (membership-gated) ---------- @Get(":id/activity") activity( @Param("id") id: string, @Query("cursor") cursor: string | undefined, @Query("limit") limit: string | undefined, @Req() req: Request ) { return this.workspaces.activity(id, requireActor(req), { cursor, limit }); } }