feat(cc-053): TasksController — taskuri si operatiuni

This commit is contained in:
admin-valentin 2026-08-01 10:43:36 +00:00
parent 93e76d4487
commit 929f04f9a6

View file

@ -1,43 +1,106 @@
import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { IsNumber, IsOptional, IsString } from 'class-validator';
import { and, asc, desc, eq, isNull, inArray, lt, gte } from 'drizzle-orm';
import { CurrentSession } from '../auth/session.decorator';
import type { AuthenticatedSession } from '../auth/tenant.guard';
import { CreateTaskDto, TASK_STATUSES, UpdateTaskDto, type TaskStatusValue } from './dto';
import { TasksService } from './tasks.service';
import { BadRequestException } from '@nestjs/common';
import { db } from '../db/client';
import { tasks } from '../db/schema';
class CreateTaskDto {
@IsString() title: string;
@IsOptional() @IsNumber() priority?: number;
@IsOptional() @IsString() dueAt?: string;
@IsOptional() @IsString() workspaceId?: string;
@IsOptional() @IsString() sourceEntityType?: string;
@IsOptional() @IsString() sourceEntityId?: string;
}
class UpdateTaskDto {
@IsOptional() @IsString() status?: string;
@IsOptional() @IsNumber() priority?: number;
@IsOptional() @IsString() dueAt?: string;
@IsOptional() @IsString() title?: string;
}
const ACTIVE_STATUSES = ['open', 'in_progress', 'blocked'] as const;
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Get()
list(@CurrentSession() session: AuthenticatedSession, @Query('status') status?: string) {
if (status && !TASK_STATUSES.includes(status as TaskStatusValue)) {
throw new BadRequestException(`status must be one of: ${TASK_STATUSES.join(', ')}`);
}
return this.tasksService.list(session, status as TaskStatusValue | undefined);
async list(
@CurrentSession() session: AuthenticatedSession,
@Query('status') status?: string,
@Query('limit') limitStr?: string,
) {
const limit = Math.min(Math.max(1, parseInt(limitStr ?? '100', 10) || 100), 500);
const statuses = status
? [status]
: [...ACTIVE_STATUSES];
return db.query.tasks.findMany({
where: and(
eq(tasks.tenantId, session.tenantId),
isNull(tasks.deletedAt),
inArray(tasks.status, statuses as any[]),
),
orderBy: [asc(tasks.priority), asc(tasks.dueAt), desc(tasks.createdAt)],
limit,
});
}
@Get(':id')
getById(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) {
return this.tasksService.getById(session, id);
@Get('overdue')
async overdue(@CurrentSession() session: AuthenticatedSession) {
const now = new Date();
return db.query.tasks.findMany({
where: and(
eq(tasks.tenantId, session.tenantId),
isNull(tasks.deletedAt),
inArray(tasks.status, [...ACTIVE_STATUSES] as any[]),
lt(tasks.dueAt, now),
),
orderBy: [asc(tasks.dueAt)],
limit: 50,
});
}
@Post()
create(@CurrentSession() session: AuthenticatedSession, @Body() dto: CreateTaskDto) {
return this.tasksService.create(session, dto);
async create(
@CurrentSession() session: AuthenticatedSession,
@Body() dto: CreateTaskDto,
) {
const [created] = await db
.insert(tasks)
.values({
tenantId: session.tenantId,
workspaceId: dto.workspaceId ?? null,
ownerUserId: session.userId,
title: dto.title,
priority: dto.priority ?? 3,
dueAt: dto.dueAt ? new Date(dto.dueAt) : null,
sourceEntityType: dto.sourceEntityType ?? null,
sourceEntityId: dto.sourceEntityId ? dto.sourceEntityId as any : null,
})
.returning();
return created;
}
@Patch(':id')
update(
async update(
@CurrentSession() session: AuthenticatedSession,
@Param('id', ParseUUIDPipe) id: string,
@Param('id') id: string,
@Body() dto: UpdateTaskDto,
) {
return this.tasksService.update(session, id, dto);
}
@Delete(':id')
softDelete(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) {
return this.tasksService.softDelete(session, id);
const [updated] = await db
.update(tasks)
.set({
...(dto.status !== undefined ? { status: dto.status as any } : {}),
...(dto.priority !== undefined ? { priority: dto.priority } : {}),
...(dto.dueAt !== undefined ? { dueAt: dto.dueAt ? new Date(dto.dueAt) : null } : {}),
...(dto.title !== undefined ? { title: dto.title } : {}),
updatedAt: new Date(),
})
.where(and(eq(tasks.id, id), eq(tasks.tenantId, session.tenantId)))
.returning();
return updated;
}
}