feat(cc-053): TasksController — taskuri si operatiuni
This commit is contained in:
parent
93e76d4487
commit
929f04f9a6
1 changed files with 87 additions and 24 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue