# Exemplos de Lógica de Negócios com Fast CRUD API

Este documento demonstra como adicionar lógica de negócios personalizada aos endpoints gerados pelo `fast-crud-api` sem modificar o código da biblioteca. Utilizamos os recursos nativos do Fastify para estender a funcionalidade.

## Índice

1. [Hooks Globais e Específicos](#hooks-globais-e-específicos)
2. [Autenticação e Autorização](#autenticação-e-autorização)
3. [Recursos User-Scoped](#recursos-user-scoped)
4. [Validação Personalizada](#validação-personalizada)
5. [Processamento de Dados](#processamento-de-dados)
6. [Filtros Avançados](#filtros-avançados)
7. [Exemplo Completo](#exemplo-completo)

## Hooks Globais e Específicos

O Fastify permite adicionar hooks que executam lógica antes ou depois de uma requisição ser processada.

```javascript
const fastify = require('fastify')();
const fastCrudApi = require('@ferjssilva/fast-crud-api');
const User = require('./models/User');

// Hook global para todas as rotas
fastify.addHook('preHandler', (request, reply, done) => {
  console.log(`${request.method} ${request.url} - ${new Date().toISOString()}`);
  done();
});

// Hook específico para rotas de usuário
fastify.register(async (userRoutes) => {
  userRoutes.addHook('preHandler', (request, reply, done) => {
    console.log('Acessando rota de usuário');
    done();
  });
  
}, { prefix: '/api/users' });

// Registrar o plugin após configurar os hooks
fastify.register(fastCrudApi, {
  prefix: '/api',
  models: [User]
});
```

## Autenticação e Autorização

Controle de acesso para rotas específicas usando hooks:

```javascript
const fastify = require('fastify')();
const fastCrudApi = require('@ferjssilva/fast-crud-api');
const User = require('./models/User');
const Post = require('./models/Post');

// Função para verificar token JWT
const verifyToken = async (request) => {
  const token = request.headers.authorization?.split(' ')[1];
  // Implemente sua lógica de verificação de token
  return token ? { id: 'user123', role: 'admin' } : null;
};

// Hook de autenticação para todas as rotas
fastify.addHook('preHandler', async (request, reply) => {
  if (request.url.startsWith('/api/admin')) {
    const user = await verifyToken(request);
    
    if (!user) {
      return reply.code(401).send({
        error: 'Unauthorized',
        message: 'Token de autenticação inválido ou ausente'
      });
    }
    
    if (user.role !== 'admin') {
      return reply.code(403).send({
        error: 'Forbidden',
        message: 'Apenas administradores podem acessar este recurso'
      });
    }
    
    // Armazenar usuário autenticado para uso posterior
    request.user = user;
  }
});

// API pública
fastify.register(fastCrudApi, {
  prefix: '/api',
  models: [Post],
  methods: {
    posts: ['GET'] // Apenas leitura pública
  }
});

// API administrativa protegida
fastify.register(async (adminApi) => {
  adminApi.register(fastCrudApi, {
    prefix: '', // Já estamos em /api/admin
    models: [User, Post],
    methods: {
      users: ['GET', 'POST', 'PUT', 'DELETE'],
      posts: ['POST', 'PUT', 'DELETE']
    }
  });
}, { prefix: '/api/admin' });
```

## Recursos User-Scoped

O `fast-crud-api` oferece suporte nativo para recursos isolados por usuário, onde cada usuário acessa apenas seus próprios dados. Esta funcionalidade é ideal para aplicações multi-tenant.

### Configuração Básica

```javascript
const fastify = require('fastify')();
const fastCrudApi = require('@ferjssilva/fast-crud-api');
const UserHabit = require('./models/UserHabit');
const UserProfile = require('./models/UserProfile');

// Middleware de autenticação - Define request.userId
fastify.addHook('preHandler', async (request, reply) => {
  const token = request.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return; // Permitir rotas públicas continuarem
  }
  
  try {
    // Verificar e decodificar o token (use sua lógica de autenticação)
    const user = await verifyJWT(token);
    request.userId = user.id; // Definir userId no request
  } catch (error) {
    // Token inválido - não definir userId
    console.error('Token inválido:', error);
  }
});

// Registrar recursos user-scoped
fastify.register(fastCrudApi, {
  prefix: '/api',
  models: [UserHabit, UserProfile],
  userScoped: ['user-habits', 'user-profiles'] // Recursos isolados por usuário
});
```

### Como Funciona

Quando um recurso é marcado como `userScoped`:

1. **Autenticação Obrigatória**: Todas as operações requerem `request.userId`
2. **Filtragem Automática**: 
   - `GET` - Filtra automaticamente por `userId`
   - `POST` - Injeta `userId` nos novos documentos
   - `PUT/DELETE` - Verifica propriedade atomicamente
3. **Segurança**:
   - Usuários não podem acessar dados de outros usuários
   - Usuários não podem modificar o `userId` dos seus recursos
   - Verificações atômicas previnem vulnerabilidades TOCTOU

### Exemplo de Modelo User-Scoped

```javascript
// models/UserHabit.js
const mongoose = require('mongoose');

const userHabitSchema = new mongoose.Schema({
  userId: {
    type: String,
    required: true,
    index: true // Importante para performance
  },
  habitId: {
    type: String,
    required: true
  },
  frequency: {
    type: String,
    enum: ['daily', 'weekly', 'monthly'],
    default: 'daily'
  },
  completedDates: [Date],
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// Índice composto para queries eficientes
userHabitSchema.index({ userId: 1, habitId: 1 }, { unique: true });

module.exports = mongoose.model('UserHabit', userHabitSchema);
```

### Uso da API

```javascript
// Cliente fazendo requisições

// 1. Listar hábitos do usuário autenticado
// GET /api/user-habits
// Authorization: Bearer <token>
// Resposta: Apenas hábitos do usuário logado

// 2. Criar novo hábito
// POST /api/user-habits
// Authorization: Bearer <token>
// Body: { "habitId": "exercise", "frequency": "daily" }
// O userId é injetado automaticamente

// 3. Tentar acessar dados de outro usuário (bloqueado)
// GET /api/user-habits?userId=outro-usuario-id
// Resposta: 403 Forbidden - Cannot access other users' data

// 4. Atualizar hábito (apenas se pertencer ao usuário)
// PUT /api/user-habits/:id
// Authorization: Bearer <token>
// Body: { "frequency": "weekly" }
// Retorna 404 se o hábito não pertencer ao usuário

// 5. Deletar hábito (apenas se pertencer ao usuário)
// DELETE /api/user-habits/:id
// Authorization: Bearer <token>
// Retorna 404 se o hábito não pertencer ao usuário
```

### Combinando User-Scoped com Outros Recursos

```javascript
const fastify = require('fastify')();
const fastCrudApi = require('@ferjssilva/fast-crud-api');

// Modelos
const User = require('./models/User');
const Post = require('./models/Post'); // Público
const UserHabit = require('./models/UserHabit'); // User-scoped
const UserProfile = require('./models/UserProfile'); // User-scoped

// Middleware de autenticação
fastify.addHook('preHandler', async (request, reply) => {
  const token = request.headers.authorization?.split(' ')[1];
  if (token) {
    try {
      const user = await verifyJWT(token);
      request.userId = user.id;
      request.user = user;
    } catch (error) {
      // Token inválido
    }
  }
});

// Recursos públicos (sem user-scoped)
fastify.register(fastCrudApi, {
  prefix: '/api/public',
  models: [Post],
  methods: {
    posts: ['GET'] // Apenas leitura pública
  }
});

// Recursos user-scoped (requerem autenticação)
fastify.register(fastCrudApi, {
  prefix: '/api/user',
  models: [UserHabit, UserProfile],
  userScoped: ['user-habits', 'user-profiles'],
  methods: {
    'user-habits': ['GET', 'POST', 'PUT', 'DELETE'],
    'user-profiles': ['GET', 'PUT'] // Sem POST/DELETE
  }
});

// Recursos administrativos (requer role de admin)
fastify.register(async (adminApi) => {
  // Hook adicional para verificar role de admin
  adminApi.addHook('preHandler', async (request, reply) => {
    if (!request.user || request.user.role !== 'admin') {
      return reply.code(403).send({
        error: 'Forbidden',
        message: 'Acesso restrito a administradores'
      });
    }
  });
  
  adminApi.register(fastCrudApi, {
    prefix: '',
    models: [User, Post],
    methods: {
      users: ['GET', 'POST', 'PUT', 'DELETE'],
      posts: ['GET', 'POST', 'PUT', 'DELETE']
    }
  });
}, { prefix: '/api/admin' });
```

### Boas Práticas

1. **Índices no Banco de Dados**:
   ```javascript
   // Sempre adicione índice no campo userId
   userHabitSchema.index({ userId: 1 });
   
   // Considere índices compostos para queries comuns
   userHabitSchema.index({ userId: 1, createdAt: -1 });
   ```

2. **Middleware de Autenticação Robusto**:
   ```javascript
   fastify.addHook('preHandler', async (request, reply) => {
     const token = request.headers.authorization?.split(' ')[1];
     
     if (!token) {
       return; // Permitir rotas públicas
     }
     
     try {
       const decoded = await verifyJWT(token);
       
       // Validar se o usuário ainda existe
       const user = await User.findById(decoded.id);
       if (!user) {
         throw new Error('Usuário não encontrado');
       }
       
       // Verificar se o usuário está ativo
       if (!user.isActive) {
         throw new Error('Usuário inativo');
       }
       
       request.userId = user._id.toString();
       request.user = user;
       
     } catch (error) {
       // Log do erro mas não bloquear rotas públicas
       console.error('Erro de autenticação:', error.message);
     }
   });
   ```

3. **Modelo de Dados Consistente**:
   ```javascript
   // Sempre use o mesmo tipo para userId (String ou ObjectId)
   const schema = new mongoose.Schema({
     userId: {
       type: String, // ou mongoose.Schema.Types.ObjectId
       required: true,
       index: true
     },
     // outros campos...
   });
   ```

4. **Tratamento de Erros**:
   ```javascript
   // Os recursos user-scoped retornam:
   // - 401 Unauthorized: quando request.userId não está definido
   // - 403 Forbidden: quando tenta acessar dados de outro usuário
   // - 404 Not Found: quando tenta modificar/deletar recurso de outro usuário
   //   (para não vazar informação sobre existência do recurso)
   ```

### Cenários Avançados

**Compartilhamento de Recursos entre Usuários**:

```javascript
// Para recursos que podem ser compartilhados, 
// não use user-scoped. Em vez disso, use hooks personalizados:

fastify.addHook('preHandler', async (request, reply) => {
  if (request.method === 'GET' && request.url.includes('/api/shared-docs')) {
    const docId = request.params.id;
    
    // Verificar se o usuário tem acesso ao documento
    const doc = await SharedDoc.findOne({
      _id: docId,
      $or: [
        { ownerId: request.userId },
        { sharedWith: request.userId }
      ]
    });
    
    if (!doc) {
      return reply.code(404).send({
        error: 'NotFound',
        message: 'Documento não encontrado'
      });
    }
  }
});
```

## Validação Personalizada

Adicione validação avançada além dos validadores do Mongoose:

```javascript
const fastify = require('fastify')();
const fastCrudApi = require('@ferjssilva/fast-crud-api');
const User = require('./models/User');

// Funções de validação personalizada
const validateUser = (userData) => {
  const errors = [];
  
  // Validar formato de email
  if (userData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(userData.email)) {
    errors.push('Formato de email inválido');
  }
  
  // Validar complexidade de senha
  if (userData.password && 
      (userData.password.length < 8 || 
       !/[A-Z]/.test(userData.password) || 
       !/[0-9]/.test(userData.password))) {
    errors.push('A senha deve ter pelo menos 8 caracteres, uma letra maiúscula e um número');
  }
  
  return { valid: errors.length === 0, errors };
};

// Hook para validar dados de usuário antes de salvar
fastify.addHook('preHandler', (request, reply, done) => {
  if ((request.method === 'POST' || request.method === 'PUT') && 
      request.url.includes('/api/users')) {
      
    const validation = validateUser(request.body);
    
    if (!validation.valid) {
      return reply.code(400).send({
        error: 'ValidationError',
        message: 'Erro de validação',
        details: validation.errors
      });
    }
  }
  done();
});

// Registrar o plugin após configurar validação
fastify.register(fastCrudApi, {
  prefix: '/api',
  models: [User]
});
```

## Processamento de Dados

Manipulação de dados antes ou depois de operações CRUD:

```javascript
const fastify = require('fastify')();
const fastCrudApi = require('@ferjssilva/fast-crud-api');
const User = require('./models/User');
const Post = require('./models/Post');
const bcrypt = require('bcrypt');

// Pre-processamento - Hash de senha antes de salvar
fastify.addHook('preHandler', async (request, reply) => {
  if ((request.method === 'POST' || request.method === 'PUT') && 
      request.url.includes('/api/users') && 
      request.body.password) {
      
    // Hash de senha
    request.body.password = await bcrypt.hash(request.body.password, 10);
  }
});

// Pós-processamento - Limpeza de dados sensíveis
fastify.addHook('onSend', (request, reply, payload, done) => {
  if (request.url.includes('/api/users')) {
    try {
      const data = JSON.parse(payload);
      
      // Remover campos sensíveis da resposta
      if (data.password) delete data.password;
      if (data.secretKey) delete data.secretKey;
      
      done(null, JSON.stringify(data));
    } catch (err) {
      done(null, payload);
    }
  } else {
    done();
  }
});

// Registrar o plugin
fastify.register(fastCrudApi, {
  prefix: '/api',
  models: [User, Post]
});
```

## Filtros Avançados

Adicione lógica de filtragem personalizada além dos filtros básicos:

```javascript
const fastify = require('fastify')();
const fastCrudApi = require('@ferjssilva/fast-crud-api');
const User = require('./models/User');
const Post = require('./models/Post');

// Hooks para manipulação de parâmetros de consulta
fastify.addHook('preHandler', (request, reply, done) => {
  if (request.method === 'GET' && request.url.startsWith('/api/posts')) {
    // Filtro por intervalo de datas
    if (request.query.dateFrom || request.query.dateTo) {
      request.query.createdAt = {};
      
      if (request.query.dateFrom) {
        request.query.createdAt.$gte = new Date(request.query.dateFrom);
        delete request.query.dateFrom;
      }
      
      if (request.query.dateTo) {
        request.query.createdAt.$lte = new Date(request.query.dateTo);
        delete request.query.dateTo;
      }
    }
    
    // Filtro por status com valores personalizados
    if (request.query.status === 'active') {
      request.query.isPublished = true;
      request.query.isArchived = false;
      delete request.query.status;
    } else if (request.query.status === 'archived') {
      request.query.isArchived = true;
      delete request.query.status;
    }
  }
  done();
});

// Registrar o plugin
fastify.register(fastCrudApi, {
  prefix: '/api',
  models: [User, Post]
});
```

## Exemplo Completo

Um exemplo completo integrando várias técnicas:

```javascript
const fastify = require('fastify')();
const mongoose = require('mongoose');
const fastCrudApi = require('@ferjssilva/fast-crud-api');

// Modelos
const User = require('./models/User');
const Post = require('./models/Post');
const Order = require('./models/Order');
const UserHabit = require('./models/UserHabit');
const UserProfile = require('./models/UserProfile');

// Utilitários
const { verifyToken, isAdmin } = require('./utils/auth');
const { validateUser, validatePost } = require('./utils/validators');
const { sanitizeData, enrichData } = require('./utils/dataProcessors');
const { applyBusinessRules } = require('./utils/businessRules');

// Registrar decoradores para lógica de negócios
fastify.decorateRequest('applyBusinessRules', applyBusinessRules);

// 1. Hook global para logging e rastreamento
fastify.addHook('preHandler', (request, reply, done) => {
  request.requestId = Date.now().toString();
  console.log(`[${request.requestId}] ${request.method} ${request.url}`);
  
  // Adicionar headers de rastreamento
  reply.header('X-Request-ID', request.requestId);
  done();
});

// 2. Hook de autenticação
fastify.addHook('preHandler', async (request, reply) => {
  // Ignorar rotas públicas
  if (request.url.match(/^\/api\/(login|register|public)/)) {
    return;
  }
  
  try {
    // Verificar token de autenticação
    const user = await verifyToken(request);
    
    if (!user) {
      return reply.code(401).send({
        error: 'Unauthorized',
        message: 'Autenticação necessária'
      });
    }
    
    // Verificar permissões para rotas administrativas
    if (request.url.includes('/api/admin') && !isAdmin(user)) {
      return reply.code(403).send({
        error: 'Forbidden',
        message: 'Acesso negado'
      });
    }
    
    // Armazenar usuário no contexto
    request.user = user;
    
  } catch (error) {
    return reply.code(401).send({
      error: 'AuthError',
      message: error.message
    });
  }
});

// 3. Validação personalizada para cada modelo
fastify.addHook('preHandler', (request, reply, done) => {
  if (request.method !== 'POST' && request.method !== 'PUT') {
    return done();
  }
  
  let validation = { valid: true };
  
  // Selecionar validador com base na URL
  if (request.url.includes('/api/users')) {
    validation = validateUser(request.body);
  } else if (request.url.includes('/api/posts')) {
    validation = validatePost(request.body);
  }
  
  if (!validation.valid) {
    return reply.code(400).send({
      error: 'ValidationError',
      message: 'Dados inválidos',
      details: validation.errors
    });
  }
  
  done();
});

// 4. Pré-processamento de dados
fastify.addHook('preHandler', (request, reply, done) => {
  if (request.method === 'POST' || request.method === 'PUT') {
    // Enriquecer dados com metadados
    enrichData(request.body, request.user);
    
    // Aplicar regras de negócios específicas
    request.applyBusinessRules(request.body, request.url);
  }
  done();
});

// 5. Pós-processamento de resposta
fastify.addHook('onSend', (request, reply, payload, done) => {
  if (payload) {
    try {
      const data = JSON.parse(payload);
      
      // Sanitizar dados sensíveis
      const sanitized = sanitizeData(data, request.url);
      
      done(null, JSON.stringify(sanitized));
    } catch (err) {
      done(null, payload);
    }
  } else {
    done(null, payload);
  }
});

// Rotas públicas - sem autenticação
fastify.register(async (publicApi) => {
  publicApi.register(fastCrudApi, {
    prefix: '', // Já estamos em /api/public
    models: [User, Post],
    methods: {
      users: ['POST'], // Apenas para registro
      posts: ['GET']   // Leitura pública
    }
  });
}, { prefix: '/api/public' });

// API principal - requer autenticação
fastify.register(fastCrudApi, {
  prefix: '/api',
  models: [User, Post, Order],
  methods: {
    users: ['GET', 'PUT'],       // Usuários podem ler e atualizar seus dados
    posts: ['GET', 'POST', 'PUT', 'DELETE'],
    orders: ['GET', 'POST', 'PUT']
  }
});

// Recursos user-scoped - isolamento automático por usuário
fastify.register(fastCrudApi, {
  prefix: '/api/user',
  models: [UserHabit, UserProfile],
  userScoped: ['user-habits', 'user-profiles'], // Isolamento automático
  methods: {
    'user-habits': ['GET', 'POST', 'PUT', 'DELETE'],
    'user-profiles': ['GET', 'PUT']
  }
});

// API administrativa - requer permissões de admin
fastify.register(async (adminApi) => {
  adminApi.register(fastCrudApi, {
    prefix: '', // Já estamos em /api/admin
    models: [User, Post, Order],
    methods: {
      users: ['GET', 'POST', 'PUT', 'DELETE'],
      posts: ['GET', 'POST', 'PUT', 'DELETE'],
      orders: ['GET', 'POST', 'PUT', 'DELETE']
    }
  });
}, { prefix: '/api/admin' });

// Iniciar servidor
fastify.listen(3000, (err) => {
  if (err) throw err;
  console.log('Servidor rodando na porta 3000');
});
```

Este exemplo demonstra:

1. **Logging e rastreamento** para todas as requisições
2. **Autenticação e autorização** com rotas protegidas
3. **Isolamento automático por usuário** com recursos user-scoped
4. **Validação personalizada** para diferentes modelos
5. **Processamento de dados** antes e depois das operações CRUD
6. **Regras de negócios** aplicadas via decoradores
7. **Múltiplos contextos de API** (público, autenticado, user-scoped, administrativo)

Ao usar estas técnicas, você pode estender significativamente as funcionalidades do `fast-crud-api` sem modificar seu código-fonte, mantendo a separação de responsabilidades e facilitando a manutenção da sua aplicação.
