feat: invitar a un proyecto por correo a alguien que aun no es usuario
Al agregar un miembro, el popup ahora permite elegir un usuario existente o escribir un correo nuevo. Si ese correo no pertenece a nadie, se crea una cuenta en estado 'invited' (nombre/clave placeholder) y se le manda un correo con un enlace de un solo uso para que complete su registro (elige su nombre y clave). La fila de project_members se crea de una vez al invitar, asi que en cuanto acepta ya es miembro sin pasos adicionales. A diferencia del autorregistro publico, no pasa por aprobacion de un admin -- quien lo agrego a su proyecto ya lo vetó. Nuevos endpoints publicos GET /api/auth/invite y POST /api/auth/accept-invite. Nueva pagina AcceptInvitePage (/aceptar-invitacion). Migracion 004 agrega el status 'invited' al enum de users. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import * as repo from './repository.js';
|
||||
import * as usersService from '../users/service.js';
|
||||
import { HttpError } from '../../utils/http.js';
|
||||
import { logAudit } from '../../utils/audit.js';
|
||||
import { ROLES } from '../../middleware/auth.js';
|
||||
@@ -94,13 +95,30 @@ export async function recalcProjectProgress(projectId) {
|
||||
await repo.updateProgress(projectId, Math.round(avg * 100) / 100);
|
||||
}
|
||||
|
||||
export async function addMember(projectId, userId, roleInProject, currentUser) {
|
||||
// Agrega por id de usuario existente O por correo. Si el correo no pertenece a nadie todavia,
|
||||
// crea una cuenta 'invited' y le manda un correo para que complete su propio registro (nombre y
|
||||
// clave) — la fila de project_members ya queda creada de una vez, asi que en cuanto acepte la
|
||||
// invitacion ya aparece como miembro sin pasos adicionales.
|
||||
export async function addMember(projectId, { userId, email }, roleInProject, currentUser) {
|
||||
const project = await repo.findById(projectId);
|
||||
if (!project) throw new HttpError(404, 'Proyecto no encontrado');
|
||||
if (!(await canWriteProject(currentUser, project))) throw new HttpError(403, 'No tienes permisos sobre este proyecto');
|
||||
const members = await repo.addMember(projectId, userId, roleInProject);
|
||||
await logAudit({ userId: currentUser.id, action: 'add_member', entityType: 'project', entityId: projectId, detail: { userId } });
|
||||
return members;
|
||||
|
||||
let targetUserId = userId;
|
||||
let invited = false;
|
||||
if (!targetUserId) {
|
||||
if (!email) throw new HttpError(400, 'Indica un usuario existente o un correo');
|
||||
const result = await usersService.inviteUser(email, currentUser.id);
|
||||
targetUserId = result.userId;
|
||||
invited = result.invited;
|
||||
}
|
||||
|
||||
const members = await repo.addMember(projectId, targetUserId, roleInProject);
|
||||
await logAudit({
|
||||
userId: currentUser.id, action: 'add_member', entityType: 'project', entityId: projectId,
|
||||
detail: { userId: targetUserId, invited },
|
||||
});
|
||||
return { members, invited };
|
||||
}
|
||||
|
||||
export async function setMemberAdmin(projectId, userId, isAdmin, currentUser) {
|
||||
|
||||
Reference in New Issue
Block a user