diff --git a/apps/api/src/brain/brain.controller.ts b/apps/api/src/brain/brain.controller.ts index f733429..a921e01 100644 --- a/apps/api/src/brain/brain.controller.ts +++ b/apps/api/src/brain/brain.controller.ts @@ -12,11 +12,25 @@ import { AuthGuard } from "../auth/guards/auth.guard"; import { WorkspaceGuard, PermissionGuard } from "../common/guards"; import { Workspace, Permission, RequirePermission } from "../common/decorators"; +/** + * @description Controller for AI/brain operations on workspace data. + * Provides endpoints for querying, searching, and getting context across + * tasks, events, and projects within a workspace. + */ @Controller("brain") @UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class BrainController { constructor(private readonly brainService: BrainService) {} + /** + * @description Query workspace entities with flexible filtering options. + * Allows filtering tasks, events, and projects by various criteria. + * @param queryDto - Query parameters including entity types, filters, and search term + * @param workspaceId - The workspace ID (injected from request context) + * @returns Filtered tasks, events, and projects with metadata + * @throws UnauthorizedException if user lacks workspace access + * @throws ForbiddenException if user lacks required permissions + */ @Post("query") @RequirePermission(Permission.WORKSPACE_ANY) async query( @@ -26,6 +40,16 @@ export class BrainController { return this.brainService.query({ ...queryDto, workspaceId }); } + /** + * @description Get current workspace context for AI operations. + * Returns a summary of active tasks, overdue items, upcoming events, and projects. + * @param contextDto - Context options specifying which entities to include + * @param workspaceId - The workspace ID (injected from request context) + * @returns Workspace context with summary counts and optional detailed entity lists + * @throws UnauthorizedException if user lacks workspace access + * @throws ForbiddenException if user lacks required permissions + * @throws NotFoundException if workspace does not exist + */ @Get("context") @RequirePermission(Permission.WORKSPACE_ANY) async getContext( @@ -35,6 +59,16 @@ export class BrainController { return this.brainService.getContext({ ...contextDto, workspaceId }); } + /** + * @description Search across all workspace entities by text. + * Performs case-insensitive search on titles, descriptions, and locations. + * @param searchTerm - Text to search for across all entity types + * @param limit - Maximum number of results per entity type (max: 100, default: 20) + * @param workspaceId - The workspace ID (injected from request context) + * @returns Matching tasks, events, and projects with metadata + * @throws UnauthorizedException if user lacks workspace access + * @throws ForbiddenException if user lacks required permissions + */ @Get("search") @RequirePermission(Permission.WORKSPACE_ANY) async search( diff --git a/apps/api/src/brain/brain.service.ts b/apps/api/src/brain/brain.service.ts index db670fc..7204330 100644 --- a/apps/api/src/brain/brain.service.ts +++ b/apps/api/src/brain/brain.service.ts @@ -80,10 +80,21 @@ export interface BrainContext { }>; } +/** + * @description Service for querying and aggregating workspace data for AI/brain operations. + * Provides unified access to tasks, events, and projects with filtering and search capabilities. + */ @Injectable() export class BrainService { constructor(private readonly prisma: PrismaService) {} + /** + * @description Query workspace entities with flexible filtering options. + * Retrieves tasks, events, and/or projects based on specified criteria. + * @param queryDto - Query parameters including workspaceId, entity types, filters, and search term + * @returns Filtered tasks, events, and projects with metadata about the query + * @throws PrismaClientKnownRequestError if database query fails + */ async query(queryDto: BrainQueryDto): Promise { const { workspaceId, entities, search, limit = 20 } = queryDto; const includeEntities = entities || [EntityType.TASK, EntityType.EVENT, EntityType.PROJECT]; @@ -115,6 +126,14 @@ export class BrainService { }; } + /** + * @description Get current workspace context for AI operations. + * Provides a summary of active tasks, overdue items, upcoming events, and projects. + * @param contextDto - Context options including workspaceId and which entities to include + * @returns Workspace context with summary counts and optional detailed entity lists + * @throws NotFoundError if workspace does not exist + * @throws PrismaClientKnownRequestError if database query fails + */ async getContext(contextDto: BrainContextDto): Promise { const { workspaceId, @@ -203,6 +222,15 @@ export class BrainService { return context; } + /** + * @description Search across all workspace entities by text. + * Performs case-insensitive search on titles, descriptions, and locations. + * @param workspaceId - The workspace to search within + * @param searchTerm - Text to search for across all entity types + * @param limit - Maximum number of results per entity type (default: 20) + * @returns Matching tasks, events, and projects with metadata + * @throws PrismaClientKnownRequestError if database query fails + */ async search(workspaceId: string, searchTerm: string, limit: number = 20): Promise { const [tasks, events, projects] = await Promise.all([ this.queryTasks(workspaceId, undefined, searchTerm, limit), diff --git a/apps/api/src/ideas/dto/query-ideas.dto.ts b/apps/api/src/ideas/dto/query-ideas.dto.ts index 92cda3f..7d2f0bb 100644 --- a/apps/api/src/ideas/dto/query-ideas.dto.ts +++ b/apps/api/src/ideas/dto/query-ideas.dto.ts @@ -14,9 +14,8 @@ import { Type } from "class-transformer"; * DTO for querying ideas with filters and pagination */ export class QueryIdeasDto { - @IsOptional() @IsUUID("4", { message: "workspaceId must be a valid UUID" }) - workspaceId?: string; + workspaceId!: string; @IsOptional() @IsEnum(IdeaStatus, { message: "status must be a valid IdeaStatus" }) diff --git a/apps/api/src/ideas/ideas.controller.ts b/apps/api/src/ideas/ideas.controller.ts index 9e6fb88..a8975e6 100644 --- a/apps/api/src/ideas/ideas.controller.ts +++ b/apps/api/src/ideas/ideas.controller.ts @@ -8,6 +8,8 @@ import { Param, Query, UseGuards, + Request, + UnauthorizedException, } from "@nestjs/common"; import { IdeasService } from "./ideas.service"; import { @@ -17,68 +19,112 @@ 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, WorkspaceGuard, PermissionGuard) +@UseGuards(AuthGuard) 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, - @Workspace() workspaceId: string, - @CurrentUser() user: any + @Request() req: any ) { - return this.ideasService.capture(workspaceId, user.id, captureIdeaDto); + 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); } + /** + * POST /api/ideas + * Create a new idea with full categorization options + */ @Post() - @RequirePermission(Permission.WORKSPACE_MEMBER) - async create( - @Body() createIdeaDto: CreateIdeaDto, - @Workspace() workspaceId: string, - @CurrentUser() user: any - ) { - return this.ideasService.create(workspaceId, user.id, createIdeaDto); + 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); } + /** + * GET /api/ideas + * Get paginated ideas with optional filters + * Supports status, domain, project, category, and search filters + */ @Get() - @RequirePermission(Permission.WORKSPACE_ANY) - async findAll( - @Query() query: QueryIdeasDto, - @Workspace() workspaceId: string - ) { + async findAll(@Query() query: QueryIdeasDto, @Request() req: any) { + const workspaceId = req.user?.workspaceId; + if (!workspaceId) { + throw new UnauthorizedException("Authentication required"); + } return this.ideasService.findAll({ ...query, workspaceId }); } + /** + * GET /api/ideas/:id + * Get a single idea by ID + */ @Get(":id") - @RequirePermission(Permission.WORKSPACE_ANY) - async findOne(@Param("id") id: string, @Workspace() workspaceId: string) { + async findOne(@Param("id") id: string, @Request() req: any) { + const workspaceId = req.user?.workspaceId; + if (!workspaceId) { + throw new UnauthorizedException("Authentication required"); + } 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, - @Workspace() workspaceId: string, - @CurrentUser() user: any + @Request() req: any ) { - return this.ideasService.update(id, workspaceId, user.id, updateIdeaDto); + 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); } + /** + * DELETE /api/ideas/:id + * Delete an idea + */ @Delete(":id") - @RequirePermission(Permission.WORKSPACE_ADMIN) - async remove( - @Param("id") id: string, - @Workspace() workspaceId: string, - @CurrentUser() user: any - ) { - return this.ideasService.remove(id, workspaceId, user.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); } }