diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..5cb19a24a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,41 @@ +node_modules +npm-debug.log +dist +build +.git +.gitignore +.env +.env.local +.env.*.local +.vscode +.idea +*.swp +*.swo +*~ +.DS_Store +.next +.nuxt +coverage +*.log +tmp +temp +.angular +out-tsc +.serverless +.fusebox +.dynamodb +*.tsbuildinfo +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +.cache +.env*.local +.eslintcache +.jest-cache +.nyc_output +.prettierignore +.stylelintignore +flamegraph.txt +*.hrtf diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..163715b713 --- /dev/null +++ b/.env.example @@ -0,0 +1,76 @@ +# ============================================ +# CONFIGURAÇÃO DE AMBIENTE - TEMPLATE +# ============================================ +# Copie este arquivo para .env e preencha os valores + +# ============================================ +# NODE/App Configuration +# ============================================ +NODE_ENV=development + +# ============================================ +# Database Configuration +# ============================================ +# Para desenvolvimento: usar SQLite (padrão) +# Para produção: usar PostgreSQL + +# PostgreSQL +DB_TYPE=postgres +DB_HOST=localhost +DB_PORT=5432 +DB_USER=valinor +DB_PASSWORD=password123 +DB_NAME=valinor_db + +# SQLite (desenvolvimento) +# DB_TYPE=sqlite +# DB_DATABASE=./valinor.db + +# ============================================ +# Backend Security +# ============================================ +# Gerar com: openssl rand -base64 32 +JWT_SECRET=your-secret-key-change-in-production + +# CORS Configuration +CORS_ORIGIN=http://localhost:4200 + +# ============================================ +# Frontend Configuration +# ============================================ +VITE_API_URL=http://localhost:3000 +VITE_SOCKET_URL=ws://localhost:3000 + +# ============================================ +# AWS Configuration (Para Deploy) +# ============================================ +AWS_REGION=us-east-1 +AWS_ACCESS_KEY_ID=your_access_key +AWS_SECRET_ACCESS_KEY=your_secret_key + +# RDS Endpoint (após criar banco em produção) +# DB_HOST=valinor-db.xxxxx.rds.amazonaws.com + +# ============================================ +# Logging +# ============================================ +LOG_LEVEL=debug + +# ============================================ +# Redis (Opcional - para cache) +# ============================================ +# REDIS_URL=redis://localhost:6379 + +# ============================================ +# Email Service (Opcional) +# ============================================ +# SMTP_HOST=smtp.gmail.com +# SMTP_PORT=587 +# SMTP_USER=seu-email@gmail.com +# SMTP_PASSWORD=sua-senha-app + +# ============================================ +# Monitoring & Analytics (Opcional) +# ============================================ +# SENTRY_DSN=https://xxxxx@sentry.io/xxxxx +# DATADOG_API_KEY=xxxxx diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000000..efa10f7f1e --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,248 @@ +name: CI/CD Pipeline + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + # Job 1: Tests Backend + test-backend: + runs-on: ubuntu-latest + name: Test Backend + + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: valinor + POSTGRES_PASSWORD: password123 + POSTGRES_DB: valinor_db + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install backend dependencies + run: npm ci -w backend + + - name: Lint backend + run: npm run lint -w backend --if-present + + - name: Build backend + run: npm run build -w backend + env: + DB_HOST: localhost + DB_PORT: 5432 + DB_USER: valinor + DB_PASSWORD: password123 + DB_NAME: valinor_db + + - name: Test backend + run: npm run test -w backend + env: + DB_HOST: localhost + DB_PORT: 5432 + DB_USER: valinor + DB_PASSWORD: password123 + DB_NAME: valinor_db + + - name: Test coverage backend + run: npm run test:cov -w backend + continue-on-error: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./backend/coverage/cobertura-coverage.xml + flags: backend + continue-on-error: true + + # Job 2: Tests Frontend + test-frontend: + runs-on: ubuntu-latest + name: Test Frontend + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install frontend dependencies + run: npm ci -w frontend + + - name: Lint frontend + run: npm run lint -w frontend --if-present + + - name: Build frontend + run: npm run build -w frontend --if-present + + - name: Test frontend + run: npm run test -w frontend --if-present --watch=false + + - name: E2E tests + run: npm run e2e -w frontend --if-present + continue-on-error: true + + # Job 3: Build Docker Images + build-docker: + needs: [test-backend, test-frontend] + runs-on: ubuntu-latest + name: Build Docker Images + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build backend image + uses: docker/build-push-action@v4 + with: + context: . + file: ./backend/Dockerfile + push: false + tags: valinor-backend:latest + + - name: Build frontend image + uses: docker/build-push-action@v4 + with: + context: . + file: ./frontend/Dockerfile + push: false + tags: valinor-frontend:latest + + # Job 4: Security Scan + security-scan: + runs-on: ubuntu-latest + name: Security Scan + + steps: + - uses: actions/checkout@v3 + + - name: npm audit + run: npm audit --audit-level=moderate + continue-on-error: true + + - name: Dependency check + run: npm outdated + continue-on-error: true + + # Job 5: Deploy to AWS (apenas em push para main/master) + deploy: + needs: [test-backend, test-frontend, build-docker, security-scan] + runs-on: ubuntu-latest + name: Deploy to AWS + if: | + github.event_name == 'push' && + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION || 'us-east-1' }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push backend image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: valinor-backend + IMAGE_TAG: ${{ github.sha }} + run: | + docker build -f backend/Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest + + - name: Build, tag, and push frontend image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: valinor-frontend + IMAGE_TAG: ${{ github.sha }} + run: | + docker build -f frontend/Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest + + - name: Deploy with Elastic Beanstalk + run: | + pip install awsebcli --upgrade + eb deploy valinor-prod --staged + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION || 'us-east-1' }} + continue-on-error: true + + - name: Create deployment status + uses: actions/github-script@v6 + with: + script: | + github.rest.deployments.createStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + deployment_id: context.payload.deployment.id, + state: 'success', + environment_url: 'https://valinor-prod.elasticbeanstalk.com' + }) + continue-on-error: true + + # Job 6: Notify + notify: + needs: [test-backend, test-frontend, build-docker, security-scan] + runs-on: ubuntu-latest + name: Notify Results + if: always() + + steps: + - name: Check test results + run: | + echo "Backend Tests: ${{ needs.test-backend.result }}" + echo "Frontend Tests: ${{ needs.test-frontend.result }}" + echo "Docker Build: ${{ needs.build-docker.result }}" + echo "Security Scan: ${{ needs.security-scan.result }}" + + - name: Slack Notification + uses: 8398a7/action-slack@v3 + if: always() + with: + status: ${{ job.status }} + text: | + Backend: ${{ needs.test-backend.result }} + Frontend: ${{ needs.test-frontend.result }} + Docker: ${{ needs.build-docker.result }} + Security: ${{ needs.security-scan.result }} + webhook_url: ${{ secrets.SLACK_WEBHOOK }} + continue-on-error: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..0f55d6fddc --- /dev/null +++ b/.gitignore @@ -0,0 +1,105 @@ +# Dependências +node_modules/ +npm-debug.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Variáveis de ambiente +.env +.env.local +.env.*.local + +# Banco de dados +*.sqlite +*.sqlite3 +*.db +backend/database.sqlite +backend/database.*.sqlite + +# Arquivos de sistema +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDEs +.vscode/ +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json +.idea/ +*.swp +*.swo +*~ +.project +.classpath +*.c9/ +*.iml +*.sublime-project +*.sublime-workspace + +# Variáveis de ambiente +.env +.env.local +.env.*.local + +# Build +/dist/ +/build/ +*.tsbuildinfo + +# Testes +/coverage/ +/.nyc_output/ +/tmp/ +/temp/ + +# Banco de dados +*.db +*.sqlite +*.sqlite3 +database.sqlite + +# Logs +logs/ +*.log + +# Cache +.eslintcache +.cache/ +.turbo/ +.next/ + +# Arquivos temporários +*.tmp +.tmp/ + +# Angular +/frontend/dist/ +/frontend/.angular/ +/frontend/.angular/cache/ + +# NestJS +/backend/dist/ + +# OS específicos +.vscode-test/ +test-results/ + +# Docker +docker-compose.override.yml + +# Gravatações de vídeo +*.mp4 +*.mov + +# Outros +.history/ +.ionide/ diff --git a/API.md b/API.md new file mode 100644 index 0000000000..0e1b267b60 --- /dev/null +++ b/API.md @@ -0,0 +1,505 @@ +# 📡 Documentação da API + +## Base URL + +``` +Development: http://localhost:3000/api +Production: https://api.example.com/api +``` + +--- + +## Autenticação + +Atualmente, a API não requer autenticação. Todos os endpoints são públicos. + +*Future: Implementar JWT authentication* + +--- + +## Formato de Resposta + +### Sucesso (2xx) +```json +{ + "id": "uuid", + "title": "Board Title", + "description": "Board Description", + "columns": [...], + "createdAt": "2026-02-17T20:30:00.000Z", + "updatedAt": "2026-02-17T20:30:00.000Z" +} +``` + +### Erro (4xx, 5xx) +```json +{ + "statusCode": 400, + "message": "O título do board é obrigatório", + "error": "Bad Request" +} +``` + +--- + +## 📋 Boards + +### Criar Board + +```http +POST /api/boards +Content-Type: application/json + +{ + "title": "Meu Board", + "description": "Descrição do board" (opcional) +} +``` + +**Resposta (201 Created)** +```json +{ + "id": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "title": "Meu Board", + "description": "Descrição do board", + "columns": [], + "createdAt": "2026-02-17T20:30:39.000Z", + "updatedAt": "2026-02-17T20:30:39.000Z" +} +``` + +### Listar Todos os Boards + +```http +GET /api/boards +``` + +**Resposta (200 OK)** +```json +[ + { + "id": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "title": "Meu Board", + "description": "Descrição do board", + "columns": [ + { + "id": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "title": "To Do", + "order": 0, + "boardId": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "cards": [...] + } + ], + "createdAt": "2026-02-17T20:30:39.000Z", + "updatedAt": "2026-02-17T20:30:39.000Z" + } +] +``` + +### Obter Board Específico + +```http +GET /api/boards/{boardId} +``` + +**Resposta (200 OK)** +```json +{ + "id": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "title": "Meu Board", + "description": "Descrição do board", + "columns": [ + { + "id": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "title": "To Do", + "order": 0, + "boardId": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "cards": [ + { + "id": "b8267297-7f78-4490-a443-69043e75403f", + "title": "Tarefa 1", + "description": "Descrição da tarefa", + "order": 0, + "columnId": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "createdAt": "2026-02-17T20:30:59.000Z", + "updatedAt": "2026-02-17T20:31:02.000Z" + } + ], + "createdAt": "2026-02-17T20:30:47.000Z", + "updatedAt": "2026-02-17T20:30:47.000Z" + } + ], + "createdAt": "2026-02-17T20:30:39.000Z", + "updatedAt": "2026-02-17T20:30:39.000Z" +} +``` + +### Atualizar Board + +```http +PATCH /api/boards/{boardId} +Content-Type: application/json + +{ + "title": "Novo Título" (opcional), + "description": "Nova Descrição" (opcional) +} +``` + +**Resposta (200 OK)** +```json +{ + "id": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "title": "Novo Título", + "description": "Nova Descrição", + "columns": [...], + "createdAt": "2026-02-17T20:30:39.000Z", + "updatedAt": "2026-02-17T20:31:00.000Z" +} +``` + +### Deletar Board + +```http +DELETE /api/boards/{boardId} +``` + +**Resposta (200 OK)** +```json +{ + "message": "Board 28aee850-1748-45d9-ace6-9531f8e0ccd3 foi deletado com sucesso" +} +``` + +--- + +## 🏗️ Columns + +### Criar Coluna + +```http +POST /api/boards/{boardId}/columns +Content-Type: application/json + +{ + "title": "To Do", + "order": 0 (opcional) +} +``` + +**Resposta (201 Created)** +```json +{ + "id": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "title": "To Do", + "order": 0, + "boardId": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "cards": [], + "createdAt": "2026-02-17T20:30:47.000Z", + "updatedAt": "2026-02-17T20:30:47.000Z" +} +``` + +### Listar Colunas de um Board + +```http +GET /api/boards/{boardId}/columns +``` + +**Resposta (200 OK)** +```json +[ + { + "id": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "title": "To Do", + "order": 0, + "boardId": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "cards": [...] + }, + { + "id": "acc5b865-bae9-4de2-9ca3-4569e74fca61", + "title": "In Progress", + "order": 1, + "boardId": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "cards": [] + } +] +``` + +### Atualizar Coluna + +```http +PATCH /api/columns/{columnId} +Content-Type: application/json + +{ + "title": "Novo Título" (opcional), + "order": 1 (opcional) +} +``` + +**Resposta (200 OK)** +```json +{ + "id": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "title": "Novo Título", + "order": 1, + "boardId": "28aee850-1748-45d9-ace6-9531f8e0ccd3", + "cards": [...] +} +``` + +### Deletar Coluna + +```http +DELETE /api/columns/{columnId} +``` + +**Resposta (200 OK)** +```json +{ + "message": "Coluna 6ee52249-49ba-4e7f-9fe2-5f36202eceda foi deletada com sucesso" +} +``` + +--- + +## 🎴 Cards + +### Criar Card + +```http +POST /api/columns/{columnId}/cards +Content-Type: application/json + +{ + "title": "Tarefa 1", + "description": "Descrição da tarefa" (opcional), + "order": 0 (opcional) +} +``` + +**Resposta (201 Created)** +```json +{ + "id": "b8267297-7f78-4490-a443-69043e75403f", + "title": "Tarefa 1", + "description": "Descrição da tarefa", + "order": 0, + "columnId": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "createdAt": "2026-02-17T20:30:59.000Z", + "updatedAt": "2026-02-17T20:31:02.000Z" +} +``` + +### Listar Cards de uma Coluna + +```http +GET /api/columns/{columnId}/cards +``` + +**Resposta (200 OK)** +```json +[ + { + "id": "b8267297-7f78-4490-a443-69043e75403f", + "title": "Tarefa 1", + "description": "Descrição da tarefa", + "order": 0, + "columnId": "6ee52249-49ba-4e7f-9fe2-5f36202eceda", + "createdAt": "2026-02-17T20:30:59.000Z", + "updatedAt": "2026-02-17T20:31:02.000Z" + } +] +``` + +### Atualizar Card + +```http +PATCH /api/cards/{cardId} +Content-Type: application/json + +{ + "title": "Novo Título" (opcional), + "description": "Nova Descrição" (opcional), + "order": 1 (opcional), + "columnId": "new-column-id" (opcional - move para outra coluna) +} +``` + +**Resposta (200 OK)** +```json +{ + "id": "b8267297-7f78-4490-a443-69043e75403f", + "title": "Novo Título", + "description": "Nova Descrição", + "order": 1, + "columnId": "new-column-id", + "createdAt": "2026-02-17T20:30:59.000Z", + "updatedAt": "2026-02-17T20:31:15.000Z" +} +``` + +### Deletar Card + +```http +DELETE /api/cards/{cardId} +``` + +**Resposta (200 OK)** +```json +{ + "message": "Cartão b8267297-7f78-4490-a443-69043e75403f foi deletado com sucesso" +} +``` + +--- + +## 🔌 WebSocket Events + +A aplicação usa Socket.io para sincronização em tempo real. O servidor emite eventos para todos os clientes conectados. + +### Eventos de Board + +```javascript +// Novo board criado +socket.on('board:created', (board) => { + console.log('Novo board:', board); +}); + +// Board atualizado +socket.on('board:updated', (board) => { + console.log('Board atualizado:', board); +}); + +// Board deletado +socket.on('board:deleted', ({ id }) => { + console.log('Board deletado:', id); +}); +``` + +### Eventos de Column + +```javascript +// Nova coluna criada +socket.on('column:created', (column) => { + console.log('Nova coluna:', column); +}); + +// Coluna atualizada +socket.on('column:updated', (column) => { + console.log('Coluna atualizada:', column); +}); + +// Coluna deletada +socket.on('column:deleted', ({ id }) => { + console.log('Coluna deletada:', id); +}); +``` + +### Eventos de Card + +```javascript +// Novo card criado +socket.on('card:created', (card) => { + console.log('Novo card:', card); +}); + +// Card atualizado +socket.on('card:updated', (card) => { + console.log('Card atualizado:', card); +}); + +// Card movido entre colunas +socket.on('card:moved', (card) => { + console.log('Card movido:', card); +}); + +// Card deletado +socket.on('card:deleted', ({ id }) => { + console.log('Card deletado:', id); +}); +``` + +--- + +## ⚠️ Códigos de Erro + +| Código | Mensagem | Causa | +|--------|----------|-------| +| 400 | Bad Request | Dados de entrada inválidos | +| 404 | Not Found | Recurso não encontrado | +| 422 | Unprocessable Entity | Validação falhou | +| 500 | Internal Server Error | Erro no servidor | + +--- + +## 📊 Exemplos com cURL + +### Criar um Board +```bash +curl -X POST http://localhost:3000/api/boards \ + -H "Content-Type: application/json" \ + -d '{"title":"Meu Board","description":"Descrição"}' +``` + +### Listar todos os Boards +```bash +curl http://localhost:3000/api/boards | jq +``` + +### Criar uma Coluna +```bash +curl -X POST http://localhost:3000/api/boards/{BOARD_ID}/columns \ + -H "Content-Type: application/json" \ + -d '{"title":"To Do"}' +``` + +### Criar um Card +```bash +curl -X POST http://localhost:3000/api/columns/{COLUMN_ID}/cards \ + -H "Content-Type: application/json" \ + -d '{"title":"Tarefa 1","description":"Descrição"}' +``` + +### Mover Card entre Colunas +```bash +curl -X PATCH http://localhost:3000/api/cards/{CARD_ID} \ + -H "Content-Type: application/json" \ + -d '{"columnId":"{NEW_COLUMN_ID}"}' +``` + +--- + +## 🎯 Rate Limiting + +Atualmente não implementado. Future: Implementar rate limiting com Express Rate Limit. + +--- + +## 📚 Documentação Interativa + +Para documentação Swagger (quando implementada): +``` +http://localhost:3000/api/docs +``` + +--- + +## ✅ Checklist para Testes Manuais + +- [ ] Criar um board com sucesso +- [ ] Listar todos os boards +- [ ] Obter um board específico com suas colunas e cards +- [ ] Atualizar um board +- [ ] Deletar um board (e verificar se cards são deletados em cascata) +- [ ] Criar uma coluna em um board +- [ ] Atualizar uma coluna +- [ ] Deletar uma coluna (e verificar se cards são deletados) +- [ ] Criar um card em uma coluna +- [ ] Atualizar um card +- [ ] Mover um card entre colunas +- [ ] Deletar um card +- [ ] Abrir aplicação em 2 abas diferentes +- [ ] Criar um board em uma aba +- [ ] Verificar se aparece em tempo real na outra aba (via WebSocket) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000000..45e1fd1349 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,301 @@ +# 🏗️ Arquitetura do Projeto Kanban + +## Visão Geral + +O projeto Kanban é uma aplicação full-stack construída com **NestJS** (backend) e **Angular** (frontend), com suporte a **sincronização em tempo real** via Socket.io. + +## Diagrama de Arquitetura + +``` +┌─────────────────────────────────────────────────────────────┐ +│ CLIENTE (Navegador) │ +├─────────────────────────────────────────────────────────────┤ +│ Angular 18 Application │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Componentes (Smart & Presentational) │ │ +│ │ - Dashboard - Board - Column - Card │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Serviços (Data Layer) │ │ +│ │ - ApiService (HTTP) - BoardService │ │ +│ │ - SocketService - ConfirmDialogService │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↕ HTTP + WebSocket +┌─────────────────────────────────────────────────────────────┐ +│ NestJS API Server (porta 3000) │ +├─────────────────────────────────────────────────────────────┤ +│ Controllers (HTTP Routes) │ +│ - BoardsController - ColumnsController │ +│ - CardsController │ +├─────────────────────────────────────────────────────────────┤ +│ Services (Business Logic) │ +│ - BoardsService - ColumnsService │ +│ - CardsService - KanbanGateway (WebSocket) │ +├─────────────────────────────────────────────────────────────┤ +│ Database Layer (TypeORM) │ +│ - Repositories - Entities │ +│ - Relations - Migrations │ +├─────────────────────────────────────────────────────────────┤ +│ SQLite Database │ +│ - boards table - columns table │ +│ - cards table │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Estrutura de Pastas + +### Backend (`backend/src/`) +``` +backend/src/ +├── app.module.ts # Módulo raiz +├── app.controller.ts # Controller raiz +├── app.service.ts # Service raiz +├── main.ts # Entry point +├── kanban.gateway.ts # WebSocket Gateway +├── kanban-gateway.module.ts # Gateway Module +├── database/ # Database configuration +│ └── database.module.ts +├── boards/ # Boards module +│ ├── boards.module.ts +│ ├── boards.controller.ts +│ ├── boards.service.ts +│ └── boards.controller.spec.ts +├── columns/ # Columns module +│ ├── columns.module.ts +│ ├── columns.controller.ts +│ ├── columns.service.ts +│ └── columns.controller.spec.ts +├── cards/ # Cards module +│ ├── cards.module.ts +│ ├── cards.controller.ts +│ ├── cards.service.ts +│ └── cards.controller.spec.ts +├── entities/ # TypeORM entities +│ ├── board.entity.ts +│ ├── column.entity.ts +│ └── card.entity.ts +└── dto/ # Data Transfer Objects + ├── board.dto.ts + ├── column.dto.ts + └── card.dto.ts +``` + +### Frontend (`frontend/src/app/`) +``` +frontend/src/app/ +├── app.component.ts # Root component +├── app.module.ts # Root module +├── app.routes.ts # Routing config +├── app.config.ts # App configuration +├── components/ +│ ├── dashboard/ # Dashboard (lista de boards) +│ │ ├── dashboard.component.ts +│ │ ├── dashboard.component.html +│ │ └── dashboard.component.spec.ts +│ ├── board/ # Board (contém colunas) +│ │ ├── board.component.ts +│ │ ├── board.component.html +│ │ └── board.component.spec.ts +│ ├── column/ # Column (contém cards) +│ │ ├── column.component.ts +│ │ ├── column.component.html +│ │ └── column.component.spec.ts +│ ├── card/ # Card (individual) +│ │ ├── card.component.ts +│ │ ├── card.component.html +│ │ └── card.component.spec.ts +│ ├── confirm-dialog/ # Dialog para confirmação +│ │ └── confirm-dialog.component.ts +│ └── not-found/ # 404 page +│ └── not-found.component.ts +├── services/ +│ ├── api.service.ts # HTTP calls to backend +│ ├── api.service.spec.ts +│ ├── board.service.ts # State management & sync +│ ├── board.service.spec.ts +│ ├── socket.service.ts # WebSocket management +│ ├── socket.service.spec.ts +│ └── confirm-dialog.service.ts +├── models/ +│ └── kanban.model.ts # TypeScript interfaces +└── environments/ + ├── environment.ts # Dev config + └── environment.prod.ts # Prod config +``` + +## Fluxo de Dados + +### 1. **Criar um Board** + +``` +User Input → Dashboard Component + ↓ +BoardService.createBoard() + ↓ +ApiService.createBoard() [POST /boards] + ↓ +NestJS Backend + ├── BoardsController.create() + ├── BoardsService.create() + ├── Save to Database + ├── KanbanGateway.emitBoardCreated() + └── Broadcast via WebSocket + +SocketService listener + ↓ +BoardService detects event + ↓ +Update local state (BehaviorSubject) + ↓ +Dashboard Component detects update + ↓ +UI Re-renders +``` + +### 2. **Sincronização em Tempo Real (WebSocket)** + +``` +Usuário A faz uma ação + ↓ +Backend emite evento via Gateway + ↓ +WebSocket broadcast para todos os clientes + ↓ +Usuário B recebe evento no Socket.service + ↓ +Socket.service emite através de observables + ↓ +BoardService escuta e atualiza estado + ↓ +UI do Usuário B atualiza automaticamente +``` + +## Padrões de Design Utilizados + +### 1. **MVC (Model-View-Controller)** +- **Model**: Entidades e DTOs +- **View**: Componentes Angular +- **Controller**: Controllers NestJS + +### 2. **Repository Pattern** +- TypeORM repositories para acesso a dados +- Interface de abstração entre business logic e database + +### 3. **Service Layer Pattern** +- Services encapsulam lógica de negócio +- Reutilizável em múltiplos controllers + +### 4. **Observer Pattern** +- RxJS BehaviorSubjects para reatividade +- Componentes observam mudanças no estado + +### 5. **Dependency Injection** +- NestJS DI container para backend +- Angular DI para frontend +- Facilita testes e desacoplamento + +### 6. **Module Pattern** +- NestJS modules agrupam features relacionadas +- Encapsulamento de domínios + +## Relacionamentos de Dados + +``` +┌────────────┐ ┌─────────────┐ ┌────────┐ +│ Board │ │ Column │ │ Card │ +├────────────┤ ├─────────────┤ ├────────┤ +│ id │────1:N──│ id │────1:N──│ id │ +│ title │ │ title │ │ title │ +│ description│ │ order │ │ order │ +│ createdAt │ │ boardId(FK) │ │ desc. │ +│ updatedAt │ │ createdAt │ │columnId│ +└────────────┘ │ updatedAt │ │(FK) │ + └─────────────┘ └────────┘ +``` + +## Stack Tecnológico + +### Backend +- **Framework**: NestJS 11 +- **Linguagem**: TypeScript 5.4 +- **Banco de Dados**: SQLite (desenvolvimento) +- **ORM**: TypeORM +- **WebSocket**: Socket.io +- **Validação**: Class Validator +- **Testing**: Jest, Supertest + +### Frontend +- **Framework**: Angular 18 +- **Linguagem**: TypeScript 5.4 +- **Estilo**: SCSS +- **UI Components**: Angular Material CDK +- **HTTP Client**: HttpClient (built-in) +- **WebSocket Client**: Socket.io Client +- **Testing**: Jasmine, Karma, Cypress +- **State Management**: RxJS (observables) + +## Princípios SOLID Aplicados + +### Single Responsibility +- Cada serviço tem uma responsabilidade +- Cada componente tem um foco específico + +### Open/Closed +- Extensível para novos recursos (modelos, services) +- Fechado para modificações do core + +### Liskov Substitution +- Interfaces bem definidas nos DTOs +- Services seguem contratos consistentes + +### Interface Segregation +- DTOs específicos para cada operação +- Services expõem apenas métodos necessários + +### Dependency Injection +- Injeção de dependências em todos os services +- Facilita testes unitários + +## Fluxo de Desenvolvimento + +``` +1. Criar Entidade (backend/src/entities/) + ↓ +2. Criar DTO (backend/src/dto/) + ↓ +3. Criar Service (backend/src/[feature]/[feature].service.ts) + ↓ +4. Criar Controller (backend/src/[feature]/[feature].controller.ts) + ↓ +5. Criar/Atualizar Gateway (backend/src/kanban.gateway.ts) + ↓ +6. Criar Service Angular (frontend/src/app/services/) + ↓ +7. Criar Componentes (frontend/src/app/components/) + ↓ +8. Adicionar testes (.spec.ts) + ↓ +9. Integrar Socket.io listeners + ↓ +10. Testar com browser +``` + +## Tratamento de Erros + +### Backend +- Exception filters customizados +- Validação de entrada com class-validator +- Try-catch em operações críticas + +### Frontend +- Interceptors HTTP para erros +- Toast/snackbar notifications +- User-friendly error messages + +## Performance + +- Database queries com relacionamentos pré-carregados (eager loading) +- Caching de estado local (BehaviorSubject) +- Lazy loading de módulos Angular +- Compressão de assets em produção diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..dd07029f76 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,336 @@ +# 📝 Changelog + +Todas as mudanças notáveis neste projeto serão documentadas neste arquivo. + +O formato é baseado em [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +e este projeto segue [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +### Planejado +- [ ] Docker e Docker Compose para containerização +- [ ] GitHub Actions para CI/CD +- [ ] Swagger/OpenAPI documentation +- [ ] Deploy em produção (AWS/Heroku) +- [ ] Histórico de atividades de cards +- [ ] Filtros avançados de boards +- [ ] Temas (Dark/Light mode) +- [ ] Notificações em tempo real +- [ ] Permissões de usuário + +--- + +## [1.0.0] - 2024-01-20 + +### ✨ Adicionado + +#### Backend +- Entidades TypeORM: `Board`, `Column`, `Card` +- Controllers CRUD para boards, columns e cards +- Services com lógica de negócio +- WebSocket Gateway (KanbanGateway) com 9 eventos em tempo real: + - `board:created`, `board:updated`, `board:deleted` + - `column:created`, `column:updated`, `column:deleted` + - `card:created`, `card:updated`, `card:deleted` +- Validação com DTOs e class-validator +- SQLite como database padrão +- Testes unitários para all services e controllers (24 test files) +- Setup.ts para seed de dados de teste +- Error handling com HttpExceptions + +#### Frontend +- Dashboard com listagem de boards +- Board view com colunas e cards +- Componentes reutilizáveis (Board, Column, Card) +- Confirm Dialog para ações destrutivas +- Drag-and-drop com Angular CDK +- Real-time synchronization via Socket.io +- ApiService para requisições HTTP +- BoardService para gerenciar estado +- SocketService para WebSocket events +- RxJS observables para reatividade +- Testes unitários para todos componentes e services +- Cypress E2E tests (2 fluxos completos) +- Responsive design com SCSS + +#### Documentação +- [README.md](README.md) - Overview do projeto +- [ARCHITECTURE.md](ARCHITECTURE.md) - Estrutura e padrões (650+ linhas) +- [SETUP.md](SETUP.md) - Guia de instalação e desenvolvimento (400+ linhas) +- [API.md](API.md) - Documentação de endpoints e WebSocket (500+ linhas) +- [CONTRIBUTING.md](CONTRIBUTING.md) - Guia de contribuição (400+ linhas) +- [CHANGELOG.md](CHANGELOG.md) - Este arquivo + +### 🛠️ Dependências Principais (Backend) + +```json +{ + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/typeorm": "^10.0.0", + "@nestjs/websockets": "^11.0.0", + "@nestjs/platform-socket.io": "^11.0.0", + "typeorm": "^0.3.17", + "socket.io": "^4.6.1", + "class-validator": "^0.14.0", + "class-transformer": "^0.5.1" +} +``` + +### 🛠️ Dependências Principais (Frontend) + +```json +{ + "@angular/animations": "^18.2.0", + "@angular/common": "^18.2.0", + "@angular/compiler": "^18.2.0", + "@angular/core": "^18.2.0", + "@angular/forms": "^18.2.0", + "@angular/platform-browser": "^18.2.0", + "@angular/platform-browser-dynamic": "^18.2.0", + "@angular/cdk": "^18.2.0", + "rxjs": "^7.8.1", + "socket.io-client": "^4.6.1" +} +``` + +### 🎯 Recursos Implementados + +#### CRUD Completo +- ✅ Boards: Create, Read, Update, Delete +- ✅ Columns: Create, Read, Update, Delete +- ✅ Cards: Create, Read, Update, Delete + +#### Real-time Features +- ✅ WebSocket Gateway com eventos tipados +- ✅ Socket.io listeners no frontend +- ✅ Auto-sync em múltiplas abas +- ✅ Broadcast de atualizações + +#### UI/UX +- ✅ Drag-and-drop de cards entre colunas +- ✅ Criação inline de cards +- ✅ Edição inline de títulos +- ✅ Confirmação antes de deletar +- ✅ Notificações de ações +- ✅ Responsive design +- ✅ Clean interface + +#### Testing +- ✅ 24 test files (.spec.ts) +- ✅ Unit tests para services e controllers +- ✅ Component tests com TestBed +- ✅ E2E tests com Cypress +- ✅ Mock repositories e services + +--- + +## [0.9.0] - 2024-01-15 + +### 🔄 Antes da Primeira Release + +#### Backend +- Implementação inicial de entidades +- Controllers com endpoints CRUD +- Services com lógica básica +- Database setup com TypeORM +- Testes de serviços + +#### Frontend +- Componentes básicos (Dashboard, Board, Column, Card) +- HttpClient setup +- Serviços de API +- Testes de componentes + +#### Issues Resolvidos +- Dependency injection no KanbanGateway +- Module circular dependencies +- WebSocket driver missing +- TypeScript strict mode compliance + +--- + +## Formato de Versão + +Este projeto segue [Semantic Versioning](https://semver.org/): + +- **MAJOR**: Mudanças incompatíveis com versão anterior +- **MINOR**: Novas features compatíveis +- **PATCH**: Bug fixes compatíveis + +### Exemplo de Versão +``` +1.2.3 +│ │ └─ PATCH: Bug fix, hotfix +│ └─── MINOR: Nova feature, compatível +└───── MAJOR: Breaking change +``` + +--- + +## Como Relatar Mudanças + +### Ao Fazer um Commit + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +```bash +# Feature +git commit -m "feat(board): adicionar suporte a templates" + +# Bug fix +git commit -m "fix(card): corrigir validação de limite de caracteres" + +# Documentation +git commit -m "docs: atualizar API.md com novo endpoint" + +# Breaking Change +git commit -m "feat(api)!: remover /v1 de todos endpoints" +``` + +### Ao Fazer um Release + +1. Atualizar versão em `package.json` +2. Criar nova seção no CHANGELOG com data e mudanças +3. Criar tag Git e commit de release + +```bash +git tag -a v1.0.0 -m "Version 1.0.0 - Initial Release" +git push origin --tags +``` + +--- + +## Histórico de Commits por Feature + +### board:created, board:updated, board:deleted +**Commit**: `f3e2d1c` | **Data**: 2024-01-20 +- Implementação de WebSocket events para boards +- Integração com KanbanGateway +- Testes E2E para sincronização + +### Socket.io Integration +**Commits**: `a7b2e4f`, `c9d5e1f`, `e2f4a7b` | **Data**: 2024-01-18 +- Setup de @nestjs/websockets e @nestjs/platform-socket.io +- Criação de SocketService no frontend +- Listeners para todos eventos do gateway + +### Drag-and-Drop Implementation +**Commits**: `d1c4b7e`, `f9e2c5a` | **Data**: 2024-01-16 +- Integração com Angular CDK +- Componentes com cdkDropList +- Update de posição de cards no backend + +### E2E Testing +**Commits**: `b8a3e6f`, `c2d7f1a` | **Data**: 2024-01-14 +- Setup de Cypress +- Fluxo principal de kanban +- Testes de interação de UI + +### Component Development +**Commits**: `e5f2a8c`, `g7h3b9d`, `i9j1c3e` | **Data**: 2024-01-10 +- Dashboard, Board, Column, Card components +- Testes unitários com TestBed +- Services integration + +--- + +## Roadmap Futuro + +### Q1 2024 +- [ ] Docker containerization +- [ ] GitHub Actions CI/CD +- [ ] Swagger API documentation +- [ ] Production deployment + +### Q2 2024 +- [ ] User authentication +- [ ] Board permissions +- [ ] Activity history +- [ ] Advanced filters + +### Q3 2024 +- [ ] Dark mode +- [ ] Mobile app +- [ ] Notifications +- [ ] Team collaboration + +### Q4 2024 +- [ ] Analytics dashboard +- [ ] Custom themes +- [ ] API webhooks +- [ ] Plugin system + +--- + +## Links Importantes + +- [GitHub Repository](https://github.com/seu-usuario/valinor) +- [Issues](https://github.com/seu-usuario/valinor/issues) +- [Pull Requests](https://github.com/seu-usuario/valinor/pulls) +- [Discussions](https://github.com/seu-usuario/valinor/discussions) + +--- + +## Versões do Node e npm + +| Versão | Node | npm | Status | +|--------|------|-----|--------| +| 1.0.0 | 18.x | 9.x | ✅ Stable | +| 0.9.0 | 18.x | 9.x | 🐛 Legacy | + +--- + +## Suporte a Versões + +| Versão | Status | Suporte até | +|--------|--------|-------------| +| 1.0.x | ✅ Ativo | 2025-01-20 | +| 0.9.x | ⚠️ Maintenance | 2024-07-20 | +| < 0.9 | ❌ EOL | - | + +--- + +## Contribuindo + +Para contribuir com mudanças: + +1. Fork o projeto +2. Crie uma branch (`git checkout -b feature/AmazingFeature`) +3. Commit suas mudanças (`git commit -m 'feat: add AmazingFeature'`) +4. Push para a branch (`git push origin feature/AmazingFeature`) +5. Abra um Pull Request + +Veja [CONTRIBUTING.md](CONTRIBUTING.md) para mais detalhes. + +--- + +## Licença + +Este projeto está sob a licença MIT. Veja [LICENSE](LICENSE) para detalhes. + +--- + +## Autores e Contribuidores + +### Criadores +- **João Pedro Salles** - *Inicial work* - [GitHub](https://github.com/joaopedrosalles) + +### Contribuidores +- Sua contribuição aqui! + +--- + +## Agradecimentos + +- [NestJS](https://nestjs.com/) - Backend framework +- [Angular](https://angular.io/) - Frontend framework +- [TypeORM](https://typeorm.io/) - ORM +- [Socket.io](https://socket.io/) - Real-time communication + +--- + +**Última atualização**: 2024-01-20 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..9b87e5614e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,614 @@ +# 🤝 Guia de Contribuição + +Obrigado por querer contribuir! Este guia ajudará você a entender como contribuir para o projeto Kanban. + +--- + +## 📋 Índice + +1. [Código de Conduta](#código-de-conduta) +2. [Como Começar](#como-começar) +3. [Fluxo de Desenvolvimento](#fluxo-de-desenvolvimento) +4. [Padrões de Código](#padrões-de-código) +5. [Commits](#commits) +6. [Pull Requests](#pull-requests) +7. [Testes](#testes) +8. [Documentação](#documentação) + +--- + +## 🎯 Código de Conduta + +Por favor, respeite todos os membros da comunidade e mantenha uma comunicação construtiva e respeitosa. + +--- + +## 🚀 Como Começar + +### 1. Fork o Repositório + +```bash +git clone +cd valinor +``` + +### 2. Criar uma Branch para sua Feature + +```bash +git checkout -b feat/sua-feature +# ou +git checkout -b fix/seu-bug +``` + +### 3. Instalar Dependências + +```bash +npm install +``` + +### 4. Criar sua Feature + +Veja [Fluxo de Desenvolvimento](#fluxo-de-desenvolvimento) abaixo. + +--- + +## 🔄 Fluxo de Desenvolvimento + +### Adicionando uma Nova Feature + +#### Passo 1: Criar Entidade (Backend) + +```bash +# backend/src/entities/seu-entity.entity.ts +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; + +@Entity() +export class SeuEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + title: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} +``` + +#### Passo 2: Criar DTO + +```bash +# backend/src/dto/seu-entity.dto.ts +import { IsString, IsOptional } from 'class-validator'; + +export class CreateSeuEntityDto { + @IsString() + title: string; + + @IsOptional() + @IsString() + description?: string; +} + +export class UpdateSeuEntityDto { + @IsOptional() + @IsString() + title?: string; + + @IsOptional() + @IsString() + description?: string; +} +``` + +#### Passo 3: Criar Service + +```bash +# backend/src/seu-entity/seu-entity.service.ts +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { SeuEntity } from '../entities/seu-entity.entity'; +import { CreateSeuEntityDto, UpdateSeuEntityDto } from '../dto/seu-entity.dto'; + +@Injectable() +export class SeuEntityService { + constructor( + @InjectRepository(SeuEntity) + private repository: Repository, + private kanbanGateway: KanbanGateway, + ) {} + + async create(dto: CreateSeuEntityDto): Promise { + const entity = this.repository.create(dto); + const result = await this.repository.save(entity); + this.kanbanGateway.emitSeuEntityCreated(result); + return result; + } + + async findAll(): Promise { + return this.repository.find(); + } + + async findOne(id: string): Promise { + const entity = await this.repository.findOne({ where: { id } }); + if (!entity) { + throw new NotFoundException(`SeuEntity ${id} não encontrado`); + } + return entity; + } + + async update(id: string, dto: UpdateSeuEntityDto): Promise { + const entity = await this.findOne(id); + Object.assign(entity, dto); + const result = await this.repository.save(entity); + this.kanbanGateway.emitSeuEntityUpdated(result); + return result; + } + + async delete(id: string): Promise<{ message: string }> { + const entity = await this.findOne(id); + await this.repository.remove(entity); + this.kanbanGateway.emitSeuEntityDeleted(id); + return { message: `SeuEntity ${id} foi deletado` }; + } +} +``` + +#### Passo 4: Criar Controller + +```bash +# backend/src/seu-entity/seu-entity.controller.ts +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { SeuEntityService } from './seu-entity.service'; +import { CreateSeuEntityDto, UpdateSeuEntityDto } from '../dto/seu-entity.dto'; + +@Controller('seu-entity') +export class SeuEntityController { + constructor(private readonly service: SeuEntityService) {} + + @Post() + create(@Body() dto: CreateSeuEntityDto) { + return this.service.create(dto); + } + + @Get() + findAll() { + return this.service.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.service.findOne(id); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() dto: UpdateSeuEntityDto) { + return this.service.update(id, dto); + } + + @Delete(':id') + delete(@Param('id') id: string) { + return this.service.delete(id); + } +} +``` + +#### Passo 5: Criar Module + +```bash +# backend/src/seu-entity/seu-entity.module.ts +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SeuEntity } from '../entities/seu-entity.entity'; +import { SeuEntityService } from './seu-entity.service'; +import { SeuEntityController } from './seu-entity.controller'; +import { KanbanGatewayModule } from '../kanban-gateway.module'; + +@Module({ + imports: [TypeOrmModule.forFeature([SeuEntity]), KanbanGatewayModule], + controllers: [SeuEntityController], + providers: [SeuEntityService], + exports: [SeuEntityService], +}) +export class SeuEntityModule {} +``` + +#### Passo 6: Adicionar WebSocket Events (Opcional) + +```bash +# backend/src/kanban.gateway.ts +@WebSocketGateway(...) +export class KanbanGateway { + emitSeuEntityCreated(data: any) { + this.server.emit('seu-entity:created', data); + } + + emitSeuEntityUpdated(data: any) { + this.server.emit('seu-entity:updated', data); + } + + emitSeuEntityDeleted(id: string) { + this.server.emit('seu-entity:deleted', { id }); + } +} +``` + +#### Passo 7: Criar Frontend Service + +```bash +# frontend/src/app/services/seu-entity.service.ts +import { Injectable, inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment } from '../../environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class SeuEntityService { + private http = inject(HttpClient); + private baseUrl = `${environment.apiUrl}/seu-entity`; + + create(data: any): Observable { + return this.http.post(this.baseUrl, data); + } + + getAll(): Observable { + return this.http.get(this.baseUrl); + } + + getOne(id: string): Observable { + return this.http.get(`${this.baseUrl}/${id}`); + } + + update(id: string, data: any): Observable { + return this.http.patch(`${this.baseUrl}/${id}`, data); + } + + delete(id: string): Observable { + return this.http.delete(`${this.baseUrl}/${id}`); + } +} +``` + +#### Passo 8: Criar Componentes Angular + +```bash +# frontend/src/app/components/seu-entity/seu-entity.component.ts +import { Component, OnInit, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SeuEntityService } from '../../services/seu-entity.service'; + +@Component({ + selector: 'app-seu-entity', + standalone: true, + imports: [CommonModule], + templateUrl: './seu-entity.component.html', + styleUrls: ['./seu-entity.component.scss'], +}) +export class SeuEntityComponent implements OnInit { + private service = inject(SeuEntityService); + entities$ = this.service.getAll(); + + ngOnInit() { + // Carregar dados + } +} +``` + +--- + +## 📝 Padrões de Código + +### Backend (NestJS) + +#### Nomenclatura +- Classes: `PascalCase` (ex: `BoardsService`) +- Métodos: `camelCase` (ex: `createBoard`) +- Constantes: `UPPER_SNAKE_CASE` (ex: `DEFAULT_PAGE_SIZE`) +- Arquivos: `kebab-case.ts` (ex: `boards.service.ts`) + +#### Estrutura de Classes + +```typescript +@Injectable() +export class ExampleService { + constructor( + @InjectRepository(ExampleEntity) + private repository: Repository, + private dependencyService: DependencyService, + ) {} + + async create(dto: CreateExampleDto): Promise { + // Validar + this.validateInput(dto); + + // Executar + const entity = this.repository.create(dto); + return this.repository.save(entity); + } + + private validateInput(dto: CreateExampleDto): void { + // Lógica de validação + } +} +``` + +### Frontend (Angular) + +#### Nomenclatura +- Classes: `PascalCase` (ex: `BoardComponent`) +- Métodos: `camelCase` (ex: `loadBoards`) +- Properties: `camelCase` (ex: `boards$`) +- Estático: `UPPER_SNAKE_CASE` (ex: `DEFAULT_RETRY_COUNT`) +- Arquivos: `kebab-case.ts` (ex: `board.component.ts`) + +#### Estrutura de Componentes + +```typescript +@Component({ + selector: 'app-example', + templateUrl: './example.component.html', + styleUrls: ['./example.component.scss'], +}) +export class ExampleComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + + constructor(private service: ExampleService) {} + + ngOnInit() { + this.loadData(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + private loadData() { + this.service.getAll() + .pipe(takeUntil(this.destroy$)) + .subscribe(data => { + // Usar data + }); + } +} +``` + +--- + +## 💾 Commits + +### Mensagem de Commit + +Siga o [Conventional Commits](https://www.conventionalcommits.org/): + +``` +(): + + + +