feat(tasks): implement CRUD endpoints with Supabase integration
This commit is contained in:
@@ -135,22 +135,75 @@ Content-Type: application/json
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2. Tarefas (Tasks) - Em Desenvolvimento
|
### 2. Tarefas (Tasks)
|
||||||
|
|
||||||
#### 2.1 Listar Tarefas
|
#### 2.1 Criar Tarefa
|
||||||
|
```http
|
||||||
|
POST /tasks
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "Fazer compras",
|
||||||
|
"description": "Ir ao supermercado",
|
||||||
|
"dueDate": "2025-12-25T00:00:00Z",
|
||||||
|
"category": "compras",
|
||||||
|
"priority": "high",
|
||||||
|
"completed": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (201 Created):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Tarefa criada com sucesso",
|
||||||
|
"data": {
|
||||||
|
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
||||||
|
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"title": "Fazer compras",
|
||||||
|
"description": "Ir ao supermercado",
|
||||||
|
"completed": false,
|
||||||
|
"due_date": "2025-12-25T00:00:00Z",
|
||||||
|
"category": "compras",
|
||||||
|
"priority": "high",
|
||||||
|
"created_at": "2025-12-01T10:00:00Z",
|
||||||
|
"updated_at": "2025-12-01T10:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validações:**
|
||||||
|
- `title`: Obrigatório, mínimo 3 caracteres, máximo 255
|
||||||
|
- `description`: Opcional, máximo 2000 caracteres
|
||||||
|
- `priority`: low | medium (default) | high
|
||||||
|
- `dueDate`, `category`: Opcionais
|
||||||
|
|
||||||
|
**Erros:**
|
||||||
|
- `400 Bad Request` - Validação falhou
|
||||||
|
- `401 Unauthorized` - Token inválido
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2.2 Listar Tarefas
|
||||||
```http
|
```http
|
||||||
GET /tasks
|
GET /tasks
|
||||||
Authorization: Bearer {token}
|
Authorization: Bearer {token}
|
||||||
|
|
||||||
# Query params opcionais:
|
# Query params opcionais:
|
||||||
?status=all|completed|pending
|
?completed=true|false
|
||||||
?sort=created_at|updated_at
|
?category=compras
|
||||||
|
?priority=low|medium|high
|
||||||
|
?sortBy=created_at|due_date|priority
|
||||||
?order=asc|desc
|
?order=asc|desc
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response (200 OK):**
|
**Response (200 OK):**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Tarefas recuperadas com sucesso",
|
||||||
|
"count": 5,
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
||||||
@@ -158,18 +211,28 @@ Authorization: Bearer {token}
|
|||||||
"title": "Fazer compras",
|
"title": "Fazer compras",
|
||||||
"description": "Ir ao supermercado",
|
"description": "Ir ao supermercado",
|
||||||
"completed": false,
|
"completed": false,
|
||||||
|
"due_date": "2025-12-25T00:00:00Z",
|
||||||
|
"category": "compras",
|
||||||
|
"priority": "high",
|
||||||
"created_at": "2025-12-01T10:00:00Z",
|
"created_at": "2025-12-01T10:00:00Z",
|
||||||
"updated_at": "2025-12-01T10:00:00Z"
|
"updated_at": "2025-12-01T10:00:00Z"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"total": 1,
|
|
||||||
"page": 1
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Query Exemplos:**
|
||||||
|
- `GET /tasks?completed=false` - Tarefas pendentes
|
||||||
|
- `GET /tasks?priority=high&sortBy=due_date` - Prioridade alta, ordenadas por vencimento
|
||||||
|
- `GET /tasks?category=trabalho&order=asc` - Categoria trabalho, ordem ascendente
|
||||||
|
|
||||||
|
**Erros:**
|
||||||
|
- `400 Bad Request` - Query inválida
|
||||||
|
- `401 Unauthorized` - Token inválido
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 2.2 Obter Tarefa Específica
|
#### 2.3 Obter Tarefa Específica
|
||||||
```http
|
```http
|
||||||
GET /tasks/:id
|
GET /tasks/:id
|
||||||
Authorization: Bearer {token}
|
Authorization: Bearer {token}
|
||||||
@@ -178,49 +241,25 @@ Authorization: Bearer {token}
|
|||||||
**Response (200 OK):**
|
**Response (200 OK):**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Tarefa recuperada com sucesso",
|
||||||
|
"data": {
|
||||||
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
"title": "Fazer compras",
|
"title": "Fazer compras",
|
||||||
"description": "Ir ao supermercado",
|
"description": "Ir ao supermercado",
|
||||||
"completed": false,
|
"completed": false,
|
||||||
|
"due_date": "2025-12-25T00:00:00Z",
|
||||||
|
"category": "compras",
|
||||||
|
"priority": "high",
|
||||||
"created_at": "2025-12-01T10:00:00Z",
|
"created_at": "2025-12-01T10:00:00Z",
|
||||||
"updated_at": "2025-12-01T10:00:00Z"
|
"updated_at": "2025-12-01T10:00:00Z"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Erros:**
|
**Erros:**
|
||||||
- `404 Not Found` - Tarefa não encontrada
|
- `404 Not Found` - Tarefa não encontrada ou não pertence ao usuário
|
||||||
- `401 Unauthorized` - Token inválido
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 2.3 Criar Tarefa
|
|
||||||
```http
|
|
||||||
POST /tasks
|
|
||||||
Authorization: Bearer {token}
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "Fazer compras",
|
|
||||||
"description": "Ir ao supermercado"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response (201 Created):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"title": "Fazer compras",
|
|
||||||
"description": "Ir ao supermercado",
|
|
||||||
"completed": false,
|
|
||||||
"created_at": "2025-12-01T10:00:00Z",
|
|
||||||
"updated_at": "2025-12-01T10:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Erros:**
|
|
||||||
- `400 Bad Request` - Título obrigatório
|
|
||||||
- `401 Unauthorized` - Token inválido
|
- `401 Unauthorized` - Token inválido
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -234,23 +273,35 @@ Content-Type: application/json
|
|||||||
{
|
{
|
||||||
"title": "Fazer compras (atualizado)",
|
"title": "Fazer compras (atualizado)",
|
||||||
"description": "Ir ao supermercado e padaria",
|
"description": "Ir ao supermercado e padaria",
|
||||||
"completed": true
|
"completed": true,
|
||||||
|
"dueDate": "2025-12-20T00:00:00Z",
|
||||||
|
"priority": "medium"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response (200 OK):**
|
**Response (200 OK):**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Tarefa atualizada com sucesso",
|
||||||
|
"data": {
|
||||||
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
"id": "6b1f2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
"title": "Fazer compras (atualizado)",
|
"title": "Fazer compras (atualizado)",
|
||||||
"description": "Ir ao supermercado e padaria",
|
"description": "Ir ao supermercado e padaria",
|
||||||
"completed": true,
|
"completed": true,
|
||||||
|
"due_date": "2025-12-20T00:00:00Z",
|
||||||
|
"category": "compras",
|
||||||
|
"priority": "medium",
|
||||||
"created_at": "2025-12-01T10:00:00Z",
|
"created_at": "2025-12-01T10:00:00Z",
|
||||||
"updated_at": "2025-12-01T10:30:00Z"
|
"updated_at": "2025-12-01T10:30:00Z"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Campos opcionais:**
|
||||||
|
- Todos os campos de CreateTaskDto são opcionais em PATCH
|
||||||
|
|
||||||
**Erros:**
|
**Erros:**
|
||||||
- `404 Not Found` - Tarefa não encontrada
|
- `404 Not Found` - Tarefa não encontrada
|
||||||
- `400 Bad Request` - Dados inválidos
|
- `400 Bad Request` - Dados inválidos
|
||||||
@@ -267,6 +318,7 @@ Authorization: Bearer {token}
|
|||||||
**Response (200 OK):**
|
**Response (200 OK):**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"success": true,
|
||||||
"message": "Tarefa deletada com sucesso"
|
"message": "Tarefa deletada com sucesso"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -277,6 +329,31 @@ Authorization: Bearer {token}
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 2.6 Obter Estatísticas
|
||||||
|
```http
|
||||||
|
GET /tasks/stats
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Estatísticas recuperadas com sucesso",
|
||||||
|
"data": {
|
||||||
|
"total": 10,
|
||||||
|
"completed": 6,
|
||||||
|
"pending": 4,
|
||||||
|
"completionPercentage": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erros:**
|
||||||
|
- `401 Unauthorized` - Token inválido
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🔄 Real-time (WebSocket)
|
## 🔄 Real-time (WebSocket)
|
||||||
|
|
||||||
### Conectar ao Realtime
|
### Conectar ao Realtime
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import jwtConfig from './config/jwt.config';
|
|||||||
import { SupabaseService } from './config/supabase.service';
|
import { SupabaseService } from './config/supabase.service';
|
||||||
import { JwtStrategy } from './auth/strategies/jwt.strategy';
|
import { JwtStrategy } from './auth/strategies/jwt.strategy';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
import { TasksModule } from './tasks/tasks.module';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -40,6 +41,7 @@ import * as Joi from 'joi';
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
TasksModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService, SupabaseService, JwtStrategy],
|
providers: [AppService, SupabaseService, JwtStrategy],
|
||||||
|
|||||||
30
backend-api/src/tasks/dto/create-task.dto.ts
Normal file
30
backend-api/src/tasks/dto/create-task.dto.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { IsString, IsBoolean, IsOptional, MaxLength, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateTaskDto {
|
||||||
|
@IsString({ message: 'Título deve ser uma string' })
|
||||||
|
@MinLength(3, { message: 'Título deve ter no mínimo 3 caracteres' })
|
||||||
|
@MaxLength(255, { message: 'Título pode ter no máximo 255 caracteres' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Descrição deve ser uma string' })
|
||||||
|
@MaxLength(2000, { message: 'Descrição pode ter no máximo 2000 caracteres' })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean({ message: 'Concluído deve ser um booleano' })
|
||||||
|
completed?: boolean = false;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Data de vencimento deve ser uma string ISO' })
|
||||||
|
dueDate?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Categoria deve ser uma string' })
|
||||||
|
@MaxLength(50, { message: 'Categoria pode ter no máximo 50 caracteres' })
|
||||||
|
category?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Prioridade deve ser uma string' })
|
||||||
|
priority?: 'low' | 'medium' | 'high' = 'medium';
|
||||||
|
}
|
||||||
31
backend-api/src/tasks/dto/update-task.dto.ts
Normal file
31
backend-api/src/tasks/dto/update-task.dto.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { IsString, IsBoolean, IsOptional, MaxLength, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateTaskDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Título deve ser uma string' })
|
||||||
|
@MinLength(3, { message: 'Título deve ter no mínimo 3 caracteres' })
|
||||||
|
@MaxLength(255, { message: 'Título pode ter no máximo 255 caracteres' })
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Descrição deve ser uma string' })
|
||||||
|
@MaxLength(2000, { message: 'Descrição pode ter no máximo 2000 caracteres' })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean({ message: 'Concluído deve ser um booleano' })
|
||||||
|
completed?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Data de vencimento deve ser uma string ISO' })
|
||||||
|
dueDate?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Categoria deve ser uma string' })
|
||||||
|
@MaxLength(50, { message: 'Categoria pode ter no máximo 50 caracteres' })
|
||||||
|
category?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: 'Prioridade deve ser uma string' })
|
||||||
|
priority?: 'low' | 'medium' | 'high';
|
||||||
|
}
|
||||||
80
backend-api/src/tasks/tasks.controller.ts
Normal file
80
backend-api/src/tasks/tasks.controller.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
UseGuards,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { TasksService } from './tasks.service';
|
||||||
|
import { CreateTaskDto } from './dto/create-task.dto';
|
||||||
|
import { UpdateTaskDto } from './dto/update-task.dto';
|
||||||
|
import { JwtAuthGuard } from '../auth/guards/jwt.guard';
|
||||||
|
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||||
|
|
||||||
|
@Controller('tasks')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
export class TasksController {
|
||||||
|
constructor(private readonly tasksService: TasksService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(
|
||||||
|
@CurrentUser() userId: string,
|
||||||
|
@Body() createTaskDto: CreateTaskDto,
|
||||||
|
) {
|
||||||
|
return this.tasksService.create(userId, createTaskDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll(
|
||||||
|
@CurrentUser() userId: string,
|
||||||
|
@Query('completed') completed?: string,
|
||||||
|
@Query('category') category?: string,
|
||||||
|
@Query('priority') priority?: string,
|
||||||
|
@Query('sortBy') sortBy?: 'created_at' | 'due_date' | 'priority',
|
||||||
|
@Query('order') order?: 'asc' | 'desc',
|
||||||
|
) {
|
||||||
|
const filters = {
|
||||||
|
completed: completed === 'true' ? true : completed === 'false' ? false : undefined,
|
||||||
|
category,
|
||||||
|
priority,
|
||||||
|
sortBy,
|
||||||
|
order,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.tasksService.findAll(userId, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('stats')
|
||||||
|
getStats(@CurrentUser() userId: string) {
|
||||||
|
return this.tasksService.getStats(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@CurrentUser() userId: string,
|
||||||
|
) {
|
||||||
|
return this.tasksService.findOne(id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
update(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@CurrentUser() userId: string,
|
||||||
|
@Body() updateTaskDto: UpdateTaskDto,
|
||||||
|
) {
|
||||||
|
return this.tasksService.update(id, userId, updateTaskDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
remove(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@CurrentUser() userId: string,
|
||||||
|
) {
|
||||||
|
return this.tasksService.remove(id, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
backend-api/src/tasks/tasks.module.ts
Normal file
11
backend-api/src/tasks/tasks.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TasksService } from './tasks.service';
|
||||||
|
import { TasksController } from './tasks.controller';
|
||||||
|
import { SupabaseService } from '../config/supabase.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [TasksService, SupabaseService],
|
||||||
|
controllers: [TasksController],
|
||||||
|
exports: [TasksService],
|
||||||
|
})
|
||||||
|
export class TasksModule {}
|
||||||
203
backend-api/src/tasks/tasks.service.ts
Normal file
203
backend-api/src/tasks/tasks.service.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import { Injectable, BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
|
||||||
|
import { SupabaseService } from '../config/supabase.service';
|
||||||
|
import { CreateTaskDto } from './dto/create-task.dto';
|
||||||
|
import { UpdateTaskDto } from './dto/update-task.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TasksService {
|
||||||
|
constructor(private supabaseService: SupabaseService) {}
|
||||||
|
|
||||||
|
async create(userId: string, createTaskDto: CreateTaskDto) {
|
||||||
|
const { data, error } = await this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.insert([
|
||||||
|
{
|
||||||
|
title: createTaskDto.title,
|
||||||
|
description: createTaskDto.description || null,
|
||||||
|
completed: createTaskDto.completed || false,
|
||||||
|
due_date: createTaskDto.dueDate || null,
|
||||||
|
category: createTaskDto.category || null,
|
||||||
|
priority: createTaskDto.priority || 'medium',
|
||||||
|
user_id: userId,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.select();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new BadRequestException(`Erro ao criar tarefa: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Tarefa criada com sucesso',
|
||||||
|
data: data?.[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(
|
||||||
|
userId: string,
|
||||||
|
filters?: {
|
||||||
|
completed?: boolean;
|
||||||
|
category?: string;
|
||||||
|
priority?: string;
|
||||||
|
sortBy?: 'created_at' | 'due_date' | 'priority';
|
||||||
|
order?: 'asc' | 'desc';
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
let query = this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', userId);
|
||||||
|
|
||||||
|
// Aplicar filtros
|
||||||
|
if (filters?.completed !== undefined) {
|
||||||
|
query = query.eq('completed', filters.completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.category) {
|
||||||
|
query = query.eq('category', filters.category);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.priority) {
|
||||||
|
query = query.eq('priority', filters.priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenação
|
||||||
|
const sortBy = filters?.sortBy || 'created_at';
|
||||||
|
const order = filters?.order || 'desc';
|
||||||
|
query = query.order(sortBy, { ascending: order === 'asc' });
|
||||||
|
|
||||||
|
const { data, error } = await query;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new BadRequestException(`Erro ao buscar tarefas: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Tarefas recuperadas com sucesso',
|
||||||
|
count: data?.length || 0,
|
||||||
|
data: data || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: string, userId: string) {
|
||||||
|
const { data, error } = await this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', id)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
throw new NotFoundException('Tarefa não encontrada');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Tarefa recuperada com sucesso',
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, userId: string, updateTaskDto: UpdateTaskDto) {
|
||||||
|
// Verificar se tarefa existe e pertence ao usuário
|
||||||
|
const { data: existingTask, error: fetchError } = await this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', id)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (fetchError || !existingTask) {
|
||||||
|
throw new NotFoundException('Tarefa não encontrada');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualizar tarefa
|
||||||
|
const { data, error } = await this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.update({
|
||||||
|
...updateTaskDto,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
.eq('id', id)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.select();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new BadRequestException(`Erro ao atualizar tarefa: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Tarefa atualizada com sucesso',
|
||||||
|
data: data?.[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string, userId: string) {
|
||||||
|
// Verificar se tarefa existe e pertence ao usuário
|
||||||
|
const { data: existingTask, error: fetchError } = await this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', id)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (fetchError || !existingTask) {
|
||||||
|
throw new NotFoundException('Tarefa não encontrada');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletar tarefa
|
||||||
|
const { error } = await this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.delete()
|
||||||
|
.eq('id', id)
|
||||||
|
.eq('user_id', userId);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new BadRequestException(`Erro ao deletar tarefa: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Tarefa deletada com sucesso',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStats(userId: string) {
|
||||||
|
const { data, error } = await this.supabaseService
|
||||||
|
.getClient()
|
||||||
|
.from('tasks')
|
||||||
|
.select('completed')
|
||||||
|
.eq('user_id', userId);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new BadRequestException(`Erro ao obter estatísticas: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = data?.length || 0;
|
||||||
|
const completed = data?.filter((t) => t.completed)?.length || 0;
|
||||||
|
const pending = total - completed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Estatísticas recuperadas com sucesso',
|
||||||
|
data: {
|
||||||
|
total,
|
||||||
|
completed,
|
||||||
|
pending,
|
||||||
|
completionPercentage: total > 0 ? Math.round((completed / total) * 100) : 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user