From 48abdbba8b01380f8d755d9db0262b400070b32a Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 29 Jan 2026 20:15:33 -0600 Subject: [PATCH] fix(api): add WorkspaceGuard to controllers and fix route ordering --- apps/api/src/activity/activity.controller.ts | 55 +++---- apps/api/src/auth/auth.controller.ts | 28 +--- apps/api/src/domains/domains.controller.ts | 93 ++++-------- apps/api/src/events/events.controller.ts | 96 +++++------- apps/api/src/ideas/ideas.controller.ts | 112 +++++--------- apps/api/src/knowledge/tags.controller.ts | 145 +++---------------- apps/api/src/layouts/layouts.controller.ts | 135 ++++++----------- apps/api/src/projects/projects.controller.ts | 93 ++++-------- 8 files changed, 216 insertions(+), 541 deletions(-) diff --git a/apps/api/src/activity/activity.controller.ts b/apps/api/src/activity/activity.controller.ts index f648a1d..e4d7a85 100644 --- a/apps/api/src/activity/activity.controller.ts +++ b/apps/api/src/activity/activity.controller.ts @@ -1,59 +1,44 @@ -import { Controller, Get, Query, Param, UseGuards, Request } from "@nestjs/common"; +import { + Controller, + Get, + Query, + Param, + UseGuards, +} from "@nestjs/common"; import { ActivityService } from "./activity.service"; import { EntityType } from "@prisma/client"; import type { QueryActivityLogDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; +import { WorkspaceGuard, PermissionGuard } from "../common/guards"; +import { Workspace, Permission, RequirePermission } from "../common/decorators"; -/** - * Controller for activity log endpoints - * All endpoints require authentication - */ @Controller("activity") -@UseGuards(AuthGuard) +@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class ActivityController { constructor(private readonly activityService: ActivityService) {} - /** - * GET /api/activity - * Get paginated activity logs with optional filters - * workspaceId is extracted from authenticated user context - */ @Get() - async findAll(@Query() query: QueryActivityLogDto, @Request() req: any) { - // Extract workspaceId from authenticated user - const workspaceId = req.user?.workspaceId || query.workspaceId; + @RequirePermission(Permission.WORKSPACE_ANY) + async findAll( + @Query() query: QueryActivityLogDto, + @Workspace() workspaceId: string + ) { return this.activityService.findAll({ ...query, workspaceId }); } - /** - * GET /api/activity/:id - * Get a single activity log by ID - * workspaceId is extracted from authenticated user context - */ @Get(":id") - async findOne(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new Error("User workspaceId not found"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findOne(@Param("id") id: string, @Workspace() workspaceId: string) { return this.activityService.findOne(id, workspaceId); } - /** - * GET /api/activity/audit/:entityType/:entityId - * Get audit trail for a specific entity - * workspaceId is extracted from authenticated user context - */ @Get("audit/:entityType/:entityId") + @RequirePermission(Permission.WORKSPACE_ANY) async getAuditTrail( - @Request() req: any, @Param("entityType") entityType: EntityType, - @Param("entityId") entityId: string + @Param("entityId") entityId: string, + @Workspace() workspaceId: string ) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new Error("User workspaceId not found"); - } return this.activityService.getAuditTrail(workspaceId, entityType, entityId); } } diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index a773f65..b6a7b07 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -8,28 +8,6 @@ import { CurrentUser } from "./decorators/current-user.decorator"; export class AuthController { constructor(private readonly authService: AuthService) {} - /** - * Handle all BetterAuth routes - * BetterAuth provides built-in handlers for: - * - /auth/sign-in - * - /auth/sign-up - * - /auth/sign-out - * - /auth/callback/authentik - * - /auth/session - * etc. - * - * Note: BetterAuth expects a Fetch API-compatible Request object. - * NestJS converts the incoming Express request to be compatible at runtime. - */ - @All("*") - async handleAuth(@Req() req: Request) { - const auth = this.authService.getAuth(); - return auth.handler(req); - } - - /** - * Get current user profile (protected route example) - */ @Get("profile") @UseGuards(AuthGuard) getProfile(@CurrentUser() user: AuthUser) { @@ -39,4 +17,10 @@ export class AuthController { name: user.name, }; } + + @All("*") + async handleAuth(@Req() req: Request) { + const auth = this.authService.getAuth(); + return auth.handler(req); + } } diff --git a/apps/api/src/domains/domains.controller.ts b/apps/api/src/domains/domains.controller.ts index f48f0e0..1063ffc 100644 --- a/apps/api/src/domains/domains.controller.ts +++ b/apps/api/src/domains/domains.controller.ts @@ -8,97 +8,62 @@ import { Param, Query, UseGuards, - Request, - UnauthorizedException, } from "@nestjs/common"; import { DomainsService } from "./domains.service"; import { CreateDomainDto, UpdateDomainDto, QueryDomainsDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; +import { WorkspaceGuard, PermissionGuard } from "../common/guards"; +import { Workspace, Permission, RequirePermission } from "../common/decorators"; +import { CurrentUser } from "../auth/decorators/current-user.decorator"; -/** - * Controller for domain endpoints - * All endpoints require authentication - */ @Controller("domains") -@UseGuards(AuthGuard) +@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class DomainsController { constructor(private readonly domainsService: DomainsService) {} - /** - * POST /api/domains - * Create a new domain - */ @Post() - async create(@Body() createDomainDto: CreateDomainDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.domainsService.create(workspaceId, userId, createDomainDto); + @RequirePermission(Permission.WORKSPACE_MEMBER) + async create( + @Body() createDomainDto: CreateDomainDto, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.domainsService.create(workspaceId, user.id, createDomainDto); } - /** - * GET /api/domains - * Get paginated domains with optional filters - */ @Get() - async findAll(@Query() query: QueryDomainsDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findAll( + @Query() query: QueryDomainsDto, + @Workspace() workspaceId: string + ) { return this.domainsService.findAll({ ...query, workspaceId }); } - /** - * GET /api/domains/:id - * Get a single domain by ID - */ @Get(":id") - async findOne(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findOne(@Param("id") id: string, @Workspace() workspaceId: string) { return this.domainsService.findOne(id, workspaceId); } - /** - * PATCH /api/domains/:id - * Update a domain - */ @Patch(":id") + @RequirePermission(Permission.WORKSPACE_MEMBER) async update( @Param("id") id: string, @Body() updateDomainDto: UpdateDomainDto, - @Request() req: any + @Workspace() workspaceId: string, + @CurrentUser() user: any ) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.domainsService.update(id, workspaceId, userId, updateDomainDto); + return this.domainsService.update(id, workspaceId, user.id, updateDomainDto); } - /** - * DELETE /api/domains/:id - * Delete a domain - */ @Delete(":id") - async remove(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.domainsService.remove(id, workspaceId, userId); + @RequirePermission(Permission.WORKSPACE_ADMIN) + async remove( + @Param("id") id: string, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.domainsService.remove(id, workspaceId, user.id); } } diff --git a/apps/api/src/events/events.controller.ts b/apps/api/src/events/events.controller.ts index 275e5aa..04733cf 100644 --- a/apps/api/src/events/events.controller.ts +++ b/apps/api/src/events/events.controller.ts @@ -8,97 +8,71 @@ import { Param, Query, UseGuards, - Request, - UnauthorizedException, } from "@nestjs/common"; import { EventsService } from "./events.service"; import { CreateEventDto, UpdateEventDto, QueryEventsDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; +import { WorkspaceGuard, PermissionGuard } from "../common/guards"; +import { Workspace, Permission, RequirePermission } from "../common/decorators"; +import { CurrentUser } from "../auth/decorators/current-user.decorator"; /** * Controller for event endpoints - * All endpoints require authentication + * All endpoints require authentication and workspace context + * + * Guards are applied in order: + * 1. AuthGuard - Verifies user authentication + * 2. WorkspaceGuard - Validates workspace access and sets RLS context + * 3. PermissionGuard - Checks role-based permissions */ @Controller("events") -@UseGuards(AuthGuard) +@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class EventsController { constructor(private readonly eventsService: EventsService) {} - /** - * POST /api/events - * Create a new event - */ @Post() - async create(@Body() createEventDto: CreateEventDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.eventsService.create(workspaceId, userId, createEventDto); + @RequirePermission(Permission.WORKSPACE_MEMBER) + async create( + @Body() createEventDto: CreateEventDto, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.eventsService.create(workspaceId, user.id, createEventDto); } - /** - * GET /api/events - * Get paginated events with optional filters - */ @Get() - async findAll(@Query() query: QueryEventsDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findAll( + @Query() query: QueryEventsDto, + @Workspace() workspaceId: string + ) { return this.eventsService.findAll({ ...query, workspaceId }); } - /** - * GET /api/events/:id - * Get a single event by ID - */ @Get(":id") - async findOne(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findOne(@Param("id") id: string, @Workspace() workspaceId: string) { return this.eventsService.findOne(id, workspaceId); } - /** - * PATCH /api/events/:id - * Update an event - */ @Patch(":id") + @RequirePermission(Permission.WORKSPACE_MEMBER) async update( @Param("id") id: string, @Body() updateEventDto: UpdateEventDto, - @Request() req: any + @Workspace() workspaceId: string, + @CurrentUser() user: any ) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.eventsService.update(id, workspaceId, userId, updateEventDto); + return this.eventsService.update(id, workspaceId, user.id, updateEventDto); } - /** - * DELETE /api/events/:id - * Delete an event - */ @Delete(":id") - async remove(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.eventsService.remove(id, workspaceId, userId); + @RequirePermission(Permission.WORKSPACE_ADMIN) + async remove( + @Param("id") id: string, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.eventsService.remove(id, workspaceId, user.id); } } diff --git a/apps/api/src/ideas/ideas.controller.ts b/apps/api/src/ideas/ideas.controller.ts index a8975e6..9e6fb88 100644 --- a/apps/api/src/ideas/ideas.controller.ts +++ b/apps/api/src/ideas/ideas.controller.ts @@ -8,8 +8,6 @@ import { Param, Query, UseGuards, - Request, - UnauthorizedException, } from "@nestjs/common"; import { IdeasService } from "./ideas.service"; import { @@ -19,112 +17,68 @@ import { QueryIdeasDto, } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; +import { WorkspaceGuard, PermissionGuard } from "../common/guards"; +import { Workspace, Permission, RequirePermission } from "../common/decorators"; +import { CurrentUser } from "../auth/decorators/current-user.decorator"; -/** - * Controller for idea endpoints - * All endpoints require authentication - */ @Controller("ideas") -@UseGuards(AuthGuard) +@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class IdeasController { constructor(private readonly ideasService: IdeasService) {} - /** - * POST /api/ideas/capture - * Quick capture endpoint for rapid idea capture - * Requires minimal fields: content only (title optional) - */ @Post("capture") + @RequirePermission(Permission.WORKSPACE_MEMBER) async capture( @Body() captureIdeaDto: CaptureIdeaDto, - @Request() req: any + @Workspace() workspaceId: string, + @CurrentUser() user: any ) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.ideasService.capture(workspaceId, userId, captureIdeaDto); + return this.ideasService.capture(workspaceId, user.id, captureIdeaDto); } - /** - * POST /api/ideas - * Create a new idea with full categorization options - */ @Post() - async create(@Body() createIdeaDto: CreateIdeaDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.ideasService.create(workspaceId, userId, createIdeaDto); + @RequirePermission(Permission.WORKSPACE_MEMBER) + async create( + @Body() createIdeaDto: CreateIdeaDto, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.ideasService.create(workspaceId, user.id, createIdeaDto); } - /** - * GET /api/ideas - * Get paginated ideas with optional filters - * Supports status, domain, project, category, and search filters - */ @Get() - async findAll(@Query() query: QueryIdeasDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findAll( + @Query() query: QueryIdeasDto, + @Workspace() workspaceId: string + ) { return this.ideasService.findAll({ ...query, workspaceId }); } - /** - * GET /api/ideas/:id - * Get a single idea by ID - */ @Get(":id") - async findOne(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findOne(@Param("id") id: string, @Workspace() workspaceId: string) { return this.ideasService.findOne(id, workspaceId); } - /** - * PATCH /api/ideas/:id - * Update an idea - */ @Patch(":id") + @RequirePermission(Permission.WORKSPACE_MEMBER) async update( @Param("id") id: string, @Body() updateIdeaDto: UpdateIdeaDto, - @Request() req: any + @Workspace() workspaceId: string, + @CurrentUser() user: any ) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.ideasService.update(id, workspaceId, userId, updateIdeaDto); + return this.ideasService.update(id, workspaceId, user.id, updateIdeaDto); } - /** - * DELETE /api/ideas/:id - * Delete an idea - */ @Delete(":id") - async remove(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.ideasService.remove(id, workspaceId, userId); + @RequirePermission(Permission.WORKSPACE_ADMIN) + async remove( + @Param("id") id: string, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.ideasService.remove(id, workspaceId, user.id); } } diff --git a/apps/api/src/knowledge/tags.controller.ts b/apps/api/src/knowledge/tags.controller.ts index a37eccd..adc54c2 100644 --- a/apps/api/src/knowledge/tags.controller.ts +++ b/apps/api/src/knowledge/tags.controller.ts @@ -7,175 +7,70 @@ import { Body, Param, UseGuards, - Request, - UnauthorizedException, HttpCode, HttpStatus, } from "@nestjs/common"; import { TagsService } from "./tags.service"; import { CreateTagDto, UpdateTagDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; +import { WorkspaceGuard, PermissionGuard } from "../common/guards"; +import { Workspace, Permission, RequirePermission } from "../common/decorators"; -/** - * Controller for knowledge tag endpoints - * All endpoints require authentication and operate within workspace context - */ @Controller("knowledge/tags") -@UseGuards(AuthGuard) +@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class TagsController { constructor(private readonly tagsService: TagsService) {} - /** - * POST /api/knowledge/tags - * Create a new tag - */ @Post() + @RequirePermission(Permission.WORKSPACE_MEMBER) async create( @Body() createTagDto: CreateTagDto, - @Request() req: any - ): Promise<{ - id: string; - workspaceId: string; - name: string; - slug: string; - color: string | null; - description: string | null; - }> { - const workspaceId = req.user?.workspaceId; - - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } - + @Workspace() workspaceId: string + ) { return this.tagsService.create(workspaceId, createTagDto); } - /** - * GET /api/knowledge/tags - * List all tags in the workspace - */ @Get() - async findAll(@Request() req: any): Promise< - Array<{ - id: string; - workspaceId: string; - name: string; - slug: string; - color: string | null; - description: string | null; - _count: { - entries: number; - }; - }> - > { - const workspaceId = req.user?.workspaceId; - - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } - + @RequirePermission(Permission.WORKSPACE_ANY) + async findAll(@Workspace() workspaceId: string) { return this.tagsService.findAll(workspaceId); } - /** - * GET /api/knowledge/tags/:slug - * Get a single tag by slug - */ @Get(":slug") + @RequirePermission(Permission.WORKSPACE_ANY) async findOne( @Param("slug") slug: string, - @Request() req: any - ): Promise<{ - id: string; - workspaceId: string; - name: string; - slug: string; - color: string | null; - description: string | null; - _count: { - entries: number; - }; - }> { - const workspaceId = req.user?.workspaceId; - - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } - + @Workspace() workspaceId: string + ) { return this.tagsService.findOne(slug, workspaceId); } - /** - * PUT /api/knowledge/tags/:slug - * Update a tag - */ @Put(":slug") + @RequirePermission(Permission.WORKSPACE_MEMBER) async update( @Param("slug") slug: string, @Body() updateTagDto: UpdateTagDto, - @Request() req: any - ): Promise<{ - id: string; - workspaceId: string; - name: string; - slug: string; - color: string | null; - description: string | null; - }> { - const workspaceId = req.user?.workspaceId; - - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } - + @Workspace() workspaceId: string + ) { return this.tagsService.update(slug, workspaceId, updateTagDto); } - /** - * DELETE /api/knowledge/tags/:slug - * Delete a tag - */ @Delete(":slug") @HttpCode(HttpStatus.NO_CONTENT) + @RequirePermission(Permission.WORKSPACE_ADMIN) async remove( @Param("slug") slug: string, - @Request() req: any - ): Promise { - const workspaceId = req.user?.workspaceId; - - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } - + @Workspace() workspaceId: string + ) { await this.tagsService.remove(slug, workspaceId); } - /** - * GET /api/knowledge/tags/:slug/entries - * Get all entries with this tag - */ @Get(":slug/entries") + @RequirePermission(Permission.WORKSPACE_ANY) async getEntries( @Param("slug") slug: string, - @Request() req: any - ): Promise< - Array<{ - id: string; - slug: string; - title: string; - summary: string | null; - status: string; - visibility: string; - createdAt: Date; - updatedAt: Date; - }> - > { - const workspaceId = req.user?.workspaceId; - - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } - + @Workspace() workspaceId: string + ) { return this.tagsService.getEntriesWithTag(slug, workspaceId); } } diff --git a/apps/api/src/layouts/layouts.controller.ts b/apps/api/src/layouts/layouts.controller.ts index 095805b..7096afa 100644 --- a/apps/api/src/layouts/layouts.controller.ts +++ b/apps/api/src/layouts/layouts.controller.ts @@ -7,122 +7,75 @@ import { Body, Param, UseGuards, - Request, - UnauthorizedException, } from "@nestjs/common"; import { LayoutsService } from "./layouts.service"; import { CreateLayoutDto, UpdateLayoutDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; +import { WorkspaceGuard, PermissionGuard } from "../common/guards"; +import { Workspace, Permission, RequirePermission } from "../common/decorators"; +import { CurrentUser } from "../auth/decorators/current-user.decorator"; -/** - * Controller for user layout endpoints - * All endpoints require authentication - */ @Controller("layouts") -@UseGuards(AuthGuard) +@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class LayoutsController { constructor(private readonly layoutsService: LayoutsService) {} - /** - * GET /api/layouts - * Get all layouts for the authenticated user - */ @Get() - async findAll(@Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.layoutsService.findAll(workspaceId, userId); + @RequirePermission(Permission.WORKSPACE_ANY) + async findAll( + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.layoutsService.findAll(workspaceId, user.id); } - /** - * GET /api/layouts/:id - * Get a single layout by ID - */ - @Get(":id") - async findOne(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.layoutsService.findOne(id, workspaceId, userId); - } - - /** - * GET /api/layouts/default - * Get the default layout for the authenticated user - * Falls back to the most recently created layout if no default exists - */ @Get("default") - async findDefault(@Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.layoutsService.findDefault(workspaceId, userId); + @RequirePermission(Permission.WORKSPACE_ANY) + async findDefault( + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.layoutsService.findDefault(workspaceId, user.id); + } + + @Get(":id") + @RequirePermission(Permission.WORKSPACE_ANY) + async findOne( + @Param("id") id: string, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.layoutsService.findOne(id, workspaceId, user.id); } - /** - * POST /api/layouts - * Create a new layout - * If isDefault is true, any existing default layout will be unset - */ @Post() - async create(@Body() createLayoutDto: CreateLayoutDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.layoutsService.create(workspaceId, userId, createLayoutDto); + @RequirePermission(Permission.WORKSPACE_MEMBER) + async create( + @Body() createLayoutDto: CreateLayoutDto, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.layoutsService.create(workspaceId, user.id, createLayoutDto); } - /** - * PATCH /api/layouts/:id - * Update a layout - * If isDefault is set to true, any existing default layout will be unset - */ @Patch(":id") + @RequirePermission(Permission.WORKSPACE_MEMBER) async update( @Param("id") id: string, @Body() updateLayoutDto: UpdateLayoutDto, - @Request() req: any + @Workspace() workspaceId: string, + @CurrentUser() user: any ) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.layoutsService.update(id, workspaceId, userId, updateLayoutDto); + return this.layoutsService.update(id, workspaceId, user.id, updateLayoutDto); } - /** - * DELETE /api/layouts/:id - * Delete a layout - */ @Delete(":id") - async remove(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.layoutsService.remove(id, workspaceId, userId); + @RequirePermission(Permission.WORKSPACE_MEMBER) + async remove( + @Param("id") id: string, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.layoutsService.remove(id, workspaceId, user.id); } } diff --git a/apps/api/src/projects/projects.controller.ts b/apps/api/src/projects/projects.controller.ts index f68731b..de91b28 100644 --- a/apps/api/src/projects/projects.controller.ts +++ b/apps/api/src/projects/projects.controller.ts @@ -8,97 +8,62 @@ import { Param, Query, UseGuards, - Request, - UnauthorizedException, } from "@nestjs/common"; import { ProjectsService } from "./projects.service"; import { CreateProjectDto, UpdateProjectDto, QueryProjectsDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; +import { WorkspaceGuard, PermissionGuard } from "../common/guards"; +import { Workspace, Permission, RequirePermission } from "../common/decorators"; +import { CurrentUser } from "../auth/decorators/current-user.decorator"; -/** - * Controller for project endpoints - * All endpoints require authentication - */ @Controller("projects") -@UseGuards(AuthGuard) +@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class ProjectsController { constructor(private readonly projectsService: ProjectsService) {} - /** - * POST /api/projects - * Create a new project - */ @Post() - async create(@Body() createProjectDto: CreateProjectDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.projectsService.create(workspaceId, userId, createProjectDto); + @RequirePermission(Permission.WORKSPACE_MEMBER) + async create( + @Body() createProjectDto: CreateProjectDto, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.projectsService.create(workspaceId, user.id, createProjectDto); } - /** - * GET /api/projects - * Get paginated projects with optional filters - */ @Get() - async findAll(@Query() query: QueryProjectsDto, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findAll( + @Query() query: QueryProjectsDto, + @Workspace() workspaceId: string + ) { return this.projectsService.findAll({ ...query, workspaceId }); } - /** - * GET /api/projects/:id - * Get a single project by ID - */ @Get(":id") - async findOne(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - if (!workspaceId) { - throw new UnauthorizedException("Authentication required"); - } + @RequirePermission(Permission.WORKSPACE_ANY) + async findOne(@Param("id") id: string, @Workspace() workspaceId: string) { return this.projectsService.findOne(id, workspaceId); } - /** - * PATCH /api/projects/:id - * Update a project - */ @Patch(":id") + @RequirePermission(Permission.WORKSPACE_MEMBER) async update( @Param("id") id: string, @Body() updateProjectDto: UpdateProjectDto, - @Request() req: any + @Workspace() workspaceId: string, + @CurrentUser() user: any ) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.projectsService.update(id, workspaceId, userId, updateProjectDto); + return this.projectsService.update(id, workspaceId, user.id, updateProjectDto); } - /** - * DELETE /api/projects/:id - * Delete a project - */ @Delete(":id") - async remove(@Param("id") id: string, @Request() req: any) { - const workspaceId = req.user?.workspaceId; - const userId = req.user?.id; - - if (!workspaceId || !userId) { - throw new UnauthorizedException("Authentication required"); - } - - return this.projectsService.remove(id, workspaceId, userId); + @RequirePermission(Permission.WORKSPACE_ADMIN) + async remove( + @Param("id") id: string, + @Workspace() workspaceId: string, + @CurrentUser() user: any + ) { + return this.projectsService.remove(id, workspaceId, user.id); } }