feat(CC-063): add ProjectsController (CRUD projects with status/priority/tags)

This commit is contained in:
admin-valentin 2026-08-01 21:20:53 +00:00
parent f1ac86ea9c
commit 9f40a1b9b8

View file

@ -0,0 +1,66 @@
import { Body, Controller, Get, IsArray, IsOptional, IsString, NotFoundException, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common';
import { and, desc, eq, isNull } from 'drizzle-orm';
import { CurrentSession } from '../auth/session.decorator';
import type { AuthenticatedSession } from '../auth/tenant.guard';
import { db } from '../db/client';
import { projects } from '../db/schema';
class CreateProjectDto {
@IsString() name!: string;
@IsOptional() @IsString() description?: string;
@IsOptional() @IsString() organizationId?: string;
@IsOptional() @IsString() status?: string;
@IsOptional() @IsString() priority?: string;
@IsOptional() @IsString() startDate?: string;
@IsOptional() @IsString() dueDate?: string;
@IsOptional() @IsString() currency?: string;
@IsOptional() @IsArray() tags?: string[];
}
class UpdateProjectDto {
@IsOptional() @IsString() name?: string;
@IsOptional() @IsString() description?: string;
@IsOptional() @IsString() status?: string;
@IsOptional() @IsString() priority?: string;
@IsOptional() @IsString() startDate?: string;
@IsOptional() @IsString() dueDate?: string;
@IsOptional() @IsArray() tags?: string[];
}
@Controller('projects')
export class ProjectsController {
@Get()
async list(@CurrentSession() session: AuthenticatedSession, @Query('status') status?: string) {
const where = status
? and(eq(projects.tenantId, session.tenantId), isNull(projects.deletedAt), eq(projects.status, status))
: and(eq(projects.tenantId, session.tenantId), isNull(projects.deletedAt));
return db.query.projects.findMany({ where, orderBy: desc(projects.createdAt), limit: 200 });
}
@Post()
async create(@CurrentSession() session: AuthenticatedSession, @Body() dto: CreateProjectDto) {
const [project] = await db.insert(projects).values({
...dto,
tenantId: session.tenantId,
tags: dto.tags ?? [],
}).returning();
return project;
}
@Get(':id')
async getOne(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) {
const project = await db.query.projects.findFirst({
where: and(eq(projects.id, id), eq(projects.tenantId, session.tenantId), isNull(projects.deletedAt)),
});
if (!project) throw new NotFoundException('Project not found');
return project;
}
@Patch(':id')
async update(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateProjectDto) {
const [updated] = await db.update(projects).set({ ...dto, updatedAt: new Date() })
.where(and(eq(projects.id, id), eq(projects.tenantId, session.tenantId))).returning();
if (!updated) throw new NotFoundException('Project not found');
return updated;
}
}