import { Request, Response } from 'express'; import { query } from '../config/database.js'; import { snakeToCamel } from '../utils/mapper.js'; interface IAppUser_Postgres { id: string; logto_id: string; email: string; name: string; role: 'guest' | 'user' | 'admin'; is_banned: boolean; organization_id?: string; organization_role?: string; organization_banned?: boolean; } interface AuthRequest extends Request { appUser?: IAppUser_Postgres | any; } export const syncUser = async (req: Request, res: Response) => { try { const { logtoId, email, name, organizationId, incomingRole } = req.body; if (!logtoId || !email || !name) { return res.status(400).json({ error: 'logtoId, email e name são obrigatórios.' }); } const userResult = await query( `INSERT INTO users (logto_id, email, name, role, is_banned, updated_at) VALUES ($1, $2, $3, $4, $5, NOW()) ON CONFLICT (logto_id) DO UPDATE SET email = EXCLUDED.email, name = EXCLUDED.name, updated_at = NOW() RETURNING id, logto_id, email, name, role, is_banned`, [logtoId, email, name, 'guest', false] ); const user = userResult?.rows[0]; if (organizationId) { let appRole = 'guest'; if (incomingRole === 'org:admin') appRole = 'admin'; else if (incomingRole === 'org:member') appRole = 'user'; const memberResult = await query( `INSERT INTO user_organizations (user_id, organization_id, role, is_banned, updated_at) VALUES ($1, $2, $3, $4, NOW()) ON CONFLICT (user_id, organization_id) DO UPDATE SET updated_at = NOW() RETURNING role, is_banned`, [user.id, organizationId, appRole, false] ); const member = memberResult?.rows[0]; return res.json(snakeToCamel({ ...user, organization_role: member.role, organization_banned: member.is_banned })); } res.json(snakeToCamel(user)); } catch (error) { console.error('Error syncing user:', error); res.status(500).json({ error: 'Erro ao sincronizar usuário: ' + (error instanceof Error ? error.message : String(error)) }); } }; export const getCurrentUser = async (req: AuthRequest, res: Response) => { try { if (!req.appUser) { return res.status(404).json({ error: 'Usuário não encontrado.' }); } const organizationId = req.headers['x-organization-id'] as string; if (organizationId) { const memberResult = await query( 'SELECT role, is_banned FROM user_organizations WHERE user_id = $1 AND organization_id = $2', [req.appUser.id, organizationId] ); if (memberResult?.rows[0]) { const member = memberResult.rows[0]; return res.json(snakeToCamel({ ...req.appUser, role: member.role, is_banned: member.is_banned, organization_id: organizationId })); } } res.json(snakeToCamel(req.appUser)); } catch (error) { console.error('Error getting current user:', error); res.status(500).json({ error: 'Erro ao buscar usuário.' }); } }; export const getAllUsers = async (req: Request, res: Response) => { try { const organizationId = req.headers['x-organization-id'] as string; if (!organizationId) { return res.status(400).json({ error: 'Organização não selecionada.' }); } const result = await query( `SELECT u.id, u.email, u.name, uo.role, uo.is_banned, uo.created_at FROM users u JOIN user_organizations uo ON u.id = uo.user_id WHERE uo.organization_id = $1 ORDER BY uo.created_at DESC`, [organizationId] ); res.json((result?.rows || []).map(snakeToCamel)); } catch (error) { console.error('Error getting users:', error); res.status(500).json({ error: 'Erro ao buscar usuários.' }); } }; export const updateUserRole = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; const { role } = req.body; const organizationId = req.headers['x-organization-id'] as string; if (!organizationId) { return res.status(400).json({ error: 'Organização não selecionada.' }); } if (!['guest', 'user', 'admin'].includes(role)) { return res.status(400).json({ error: 'Role inválido.' }); } const result = await query( 'UPDATE user_organizations SET role = $1, updated_at = NOW() WHERE user_id = $2 AND organization_id = $3 RETURNING *', [role, id, organizationId] ); if (!result?.rowCount) { return res.status(404).json({ error: 'Membro não encontrado nesta organização.' }); } res.json(snakeToCamel(result.rows[0])); } catch (error) { console.error('Error updating role:', error); res.status(500).json({ error: 'Erro ao alterar role.' }); } }; export const toggleBanUser = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; const { isBanned } = req.body; const organizationId = req.headers['x-organization-id'] as string; if (!organizationId) { return res.status(400).json({ error: 'Organização não selecionada.' }); } const result = await query( 'UPDATE user_organizations SET is_banned = $1, updated_at = NOW() WHERE user_id = $2 AND organization_id = $3 RETURNING *', [isBanned, id, organizationId] ); if (!result?.rowCount) { return res.status(404).json({ error: 'Membro não encontrado.' }); } res.json(snakeToCamel(result.rows[0])); } catch (error) { console.error('Error toggling ban:', error); res.status(500).json({ error: 'Erro ao alterar status de banimento.' }); } }; export const heartbeat = async (req: AuthRequest, res: Response) => { try { if (!req.appUser) return res.status(401).end(); await query('UPDATE users SET last_seen_at = NOW() WHERE id = $1', [req.appUser.id]); res.status(200).send(); } catch (error) { console.error('Heartbeat error:', error); res.status(500).send(); } }; export const getActiveUsers = async (req: AuthRequest, res: Response) => { try { const organizationId = req.headers['x-organization-id'] as string; if (!organizationId) return res.json([]); const result = await query( `SELECT u.id, u.name, u.email, u.last_seen_at, u.logto_id FROM users u JOIN user_organizations uo ON u.id = uo.user_id WHERE uo.organization_id = $1 AND u.last_seen_at > NOW() - INTERVAL '2 minutes' AND u.id != $2`, [organizationId, req.appUser?.id] ); res.json((result?.rows || []).map(snakeToCamel)); } catch (error) { console.error('Error getting active users:', error); res.status(500).json([]); } }; export const deleteUser = async (req: Request, res: Response) => { try { const { id } = req.params; const organizationId = req.headers['x-organization-id'] as string; if (!organizationId) { return res.status(400).json({ error: 'Organização não selecionada.' }); } const result = await query( 'DELETE FROM user_organizations WHERE user_id = $1 AND organization_id = $2 RETURNING *', [id, organizationId] ); if (!result?.rowCount) { return res.status(404).json({ error: 'Membro não encontrado.' }); } res.json({ message: 'Membro removido com sucesso.' }); } catch (error) { console.error('Error deleting user:', error); res.status(500).json({ error: 'Erro ao remover membro.' }); } };