Files
GPI/src/server/controllers/userController.ts

241 lines
8.1 KiB
TypeScript

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.' });
}
};