import { Request, Response } from 'express'; import bcrypt from 'bcryptjs'; import { db } from '../config/database'; import { generateTokens } from '../middleware/auth'; import { auditLog } from '../utils/audit'; export async function register(req: Request, res: Response) { const { name, email, password, type, whatsapp, address, pj_data } = req.body; // Validations (before transaction) if (!name || !email || !password) { return res.status(400).json({ error: 'Campos obrigatórios faltando (nome, email, senha)' }); } const trx = await db.transaction(); try { const existingUser = await trx('users').where({ email }).first(); if (existingUser) { await trx.rollback(); return res.status(400).json({ error: 'E-mail já cadastrado' }); } const password_hash = await bcrypt.hash(password, 10); // Create User const [userId] = await trx('users').insert({ name, email, password_hash, whatsapp, role: type === 'partner' ? 'partner_admin' : 'user', state: address?.state || null, // city: address?.city, // Column doesn't exist yet, ignoring to avoid crash // neighborhood: address?.neighborhood, // Column doesn't exist yet status: 'active', // Users are active by default (partners rely on partner status) created_at: new Date(), updated_at: new Date() }); // Create Partner (PJ) if (type === 'partner' && pj_data) { // Check existing CNPJ if (pj_data.cnpj) { const existingPartner = await trx('partners').where({ cnpj: pj_data.cnpj }).first(); if (existingPartner) { await trx.rollback(); return res.status(400).json({ error: 'CNPJ já cadastrado' }); } } const [partnerId] = await trx('partners').insert({ company_name: pj_data.fantasy_name, slug: null, // Removed automatic random slug. User will choose via modal. cnpj: pj_data.cnpj || null, email, // Contact email phone: whatsapp, address_street: address?.street, address_number: address?.number, address_neighborhood: address?.neighborhood, address_city: address?.city, address_state: address?.state, type: String(pj_data.category_id || 'Serviços'), status: 'inactive', // 'pending' doesn't exist in enum ('active','inactive'), so using 'inactive' as pending approval owner_user_id: userId, created_at: new Date(), updated_at: new Date(), data_consent: 1 // Implicit consent }); // Link user to partner await trx('users').where({ id: userId }).update({ partner_id: partnerId }); } await trx.commit(); // Auto-login response const user = await db('users').where({ id: userId }).first(); const tokens = generateTokens(user); const redirectPath = getRedirectPath(user); // Log action (outside transaction) await auditLog(user.id, 'REGISTER', 'user', user.id, `Novo cadastro: ${type}`, req.ip); res.status(201).json({ user, redirectPath, ...tokens }); } catch (err: any) { await trx.rollback(); console.error('Register Error:', err); res.status(500).json({ error: 'Erro ao criar conta: ' + (err.message || 'Erro interno') }); } } export async function login(req: Request, res: Response) { try { const { email, password } = req.body; const user = await db('users').where({ email }).first(); if (!user || user.deleted_at || user.status === 'banned') { return res.status(401).json({ error: 'Credenciais inválidas ou conta desativada' }); } if (!(await bcrypt.compare(password, user.password_hash))) { return res.status(401).json({ error: 'Credenciais inválidas' }); } const tokens = generateTokens(user); await auditLog(user.id, 'LOGIN', 'user', user.id, 'Usuário logou no sistema', req.ip); const redirectPath = getRedirectPath(user); res.json({ user, redirectPath, ...tokens }); } catch (err: any) { res.status(500).json({ error: 'Erro no servidor' }); } } function getRedirectPath(user: any) { if (user.role === 'super_admin') return '/admin/dashboard'; if (user.role === 'partner' || user.role === 'partner_admin') { return '/partner/dashboard'; } return '/user/dashboard'; } export async function profile(req: Request, res: Response) { try { const user = await db('users') .select('users.*', 'partners.slug as partner_slug') .leftJoin('partners', 'users.partner_id', 'partners.id') .where('users.id', req.user!.id) .first(); res.json(user); } catch (err) { res.status(500).json({ error: 'Erro ao buscar perfil' }); } } export async function completeProfile(req: Request, res: Response) { try { const userId = req.user!.id; const { whatsapp, state, city, neighborhood } = req.body || {}; if (!whatsapp || !state || !city || !neighborhood) { return res.status(400).json({ error: 'Campos obrigatórios faltando' }); } await db('users').where({ id: userId }).update({ whatsapp, state, city, neighborhood, updated_at: new Date(), }); const user = await db('users').where({ id: userId }).first(); res.json(user); } catch (err: any) { res.status(500).json({ error: 'Erro ao completar cadastro' }); } }