Backend: - Add getAllUsuarios() to get users from all tenants - Add updateUsuarioGlobal() to edit users and change their tenant - Add deleteUsuarioGlobal() for global user deletion - Add global admin check based on tenant RFC - Add new API routes: /usuarios/global/* Frontend: - Add UserListItem.tenantId and tenantName fields - Add /admin/usuarios page with full user management - Support filtering by tenant and search - Inline editing for name, role, and tenant assignment - Group users by company for better organization - Add "Admin Usuarios" menu item for admin navigation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
198 lines
4.6 KiB
TypeScript
198 lines
4.6 KiB
TypeScript
import { prisma } from '../config/database.js';
|
|
import bcrypt from 'bcryptjs';
|
|
import type { UserListItem, UserInvite, UserUpdate } from '@horux/shared';
|
|
|
|
export async function getUsuarios(tenantId: string): Promise<UserListItem[]> {
|
|
const users = await prisma.user.findMany({
|
|
where: { tenantId },
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
nombre: true,
|
|
role: true,
|
|
active: true,
|
|
lastLogin: true,
|
|
createdAt: true,
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
|
|
return users.map(u => ({
|
|
...u,
|
|
lastLogin: u.lastLogin?.toISOString() || null,
|
|
createdAt: u.createdAt.toISOString(),
|
|
}));
|
|
}
|
|
|
|
export async function inviteUsuario(tenantId: string, data: UserInvite): Promise<UserListItem> {
|
|
// Check tenant user limit
|
|
const tenant = await prisma.tenant.findUnique({
|
|
where: { id: tenantId },
|
|
select: { usersLimit: true },
|
|
});
|
|
|
|
const currentCount = await prisma.user.count({ where: { tenantId } });
|
|
|
|
if (currentCount >= (tenant?.usersLimit || 1)) {
|
|
throw new Error('Límite de usuarios alcanzado para este plan');
|
|
}
|
|
|
|
// Generate temporary password
|
|
const tempPassword = Math.random().toString(36).slice(-8);
|
|
const passwordHash = await bcrypt.hash(tempPassword, 12);
|
|
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
tenantId,
|
|
email: data.email,
|
|
passwordHash,
|
|
nombre: data.nombre,
|
|
role: data.role,
|
|
},
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
nombre: true,
|
|
role: true,
|
|
active: true,
|
|
lastLogin: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
|
|
// In production, send email with tempPassword
|
|
console.log(`Temporary password for ${data.email}: ${tempPassword}`);
|
|
|
|
return {
|
|
...user,
|
|
lastLogin: user.lastLogin?.toISOString() || null,
|
|
createdAt: user.createdAt.toISOString(),
|
|
};
|
|
}
|
|
|
|
export async function updateUsuario(
|
|
tenantId: string,
|
|
userId: string,
|
|
data: UserUpdate
|
|
): Promise<UserListItem> {
|
|
const user = await prisma.user.update({
|
|
where: { id: userId, tenantId },
|
|
data: {
|
|
...(data.nombre && { nombre: data.nombre }),
|
|
...(data.role && { role: data.role }),
|
|
...(data.active !== undefined && { active: data.active }),
|
|
},
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
nombre: true,
|
|
role: true,
|
|
active: true,
|
|
lastLogin: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
|
|
return {
|
|
...user,
|
|
lastLogin: user.lastLogin?.toISOString() || null,
|
|
createdAt: user.createdAt.toISOString(),
|
|
};
|
|
}
|
|
|
|
export async function deleteUsuario(tenantId: string, userId: string): Promise<void> {
|
|
await prisma.user.delete({
|
|
where: { id: userId, tenantId },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Obtiene todos los usuarios de todas las empresas (solo admin global)
|
|
*/
|
|
export async function getAllUsuarios(): Promise<UserListItem[]> {
|
|
const users = await prisma.user.findMany({
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
nombre: true,
|
|
role: true,
|
|
active: true,
|
|
lastLogin: true,
|
|
createdAt: true,
|
|
tenantId: true,
|
|
tenant: {
|
|
select: {
|
|
nombre: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: [{ tenant: { nombre: 'asc' } }, { createdAt: 'desc' }],
|
|
});
|
|
|
|
return users.map(u => ({
|
|
id: u.id,
|
|
email: u.email,
|
|
nombre: u.nombre,
|
|
role: u.role,
|
|
active: u.active,
|
|
lastLogin: u.lastLogin?.toISOString() || null,
|
|
createdAt: u.createdAt.toISOString(),
|
|
tenantId: u.tenantId,
|
|
tenantName: u.tenant.nombre,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Actualiza un usuario globalmente (puede cambiar de tenant)
|
|
*/
|
|
export async function updateUsuarioGlobal(
|
|
userId: string,
|
|
data: UserUpdate & { tenantId?: string }
|
|
): Promise<UserListItem> {
|
|
const user = await prisma.user.update({
|
|
where: { id: userId },
|
|
data: {
|
|
...(data.nombre && { nombre: data.nombre }),
|
|
...(data.role && { role: data.role }),
|
|
...(data.active !== undefined && { active: data.active }),
|
|
...(data.tenantId && { tenantId: data.tenantId }),
|
|
},
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
nombre: true,
|
|
role: true,
|
|
active: true,
|
|
lastLogin: true,
|
|
createdAt: true,
|
|
tenantId: true,
|
|
tenant: {
|
|
select: {
|
|
nombre: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
id: user.id,
|
|
email: user.email,
|
|
nombre: user.nombre,
|
|
role: user.role,
|
|
active: user.active,
|
|
lastLogin: user.lastLogin?.toISOString() || null,
|
|
createdAt: user.createdAt.toISOString(),
|
|
tenantId: user.tenantId,
|
|
tenantName: user.tenant.nombre,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Elimina un usuario globalmente
|
|
*/
|
|
export async function deleteUsuarioGlobal(userId: string): Promise<void> {
|
|
await prisma.user.delete({
|
|
where: { id: userId },
|
|
});
|
|
}
|