Files
alrahma_sunday_school_api/apps/api/dist/services/teamService.js
T
2026-06-11 03:22:12 -04:00

166 lines
8.9 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listEmployees = listEmployees;
exports.inviteEmployee = inviteEmployee;
exports.updateEmployeeRole = updateEmployeeRole;
exports.deactivateEmployee = deactivateEmployee;
exports.reactivateEmployee = reactivateEmployee;
exports.removeEmployee = removeEmployee;
const crypto_1 = __importDefault(require("crypto"));
const prisma_1 = require("../lib/prisma");
const notificationService_1 = require("./notificationService");
const notificationLocalizationService_1 = require("./notificationLocalizationService");
const INVITE_TOKEN_TTL_MINUTES = 60 * 24 * 7;
function ensureDashboardBasePath(baseUrl) {
try {
const url = new URL(baseUrl);
const pathname = url.pathname.replace(/\/$/, '');
if (!pathname.endsWith('/dashboard'))
url.pathname = `${pathname}/dashboard`;
return url.toString().replace(/\/$/, '');
}
catch {
const trimmed = baseUrl.replace(/\/$/, '');
return trimmed.endsWith('/dashboard') ? trimmed : `${trimmed}/dashboard`;
}
}
function buildInviteEmail(resetUrl, companyName, firstName, role, locale) {
const greetingName = firstName.trim() || 'there';
if (locale === 'fr') {
return {
subject: `Vous avez ete invite chez ${companyName}`,
html: `
<p>Bonjour ${greetingName},</p>
<p>Vous avez ete invite a rejoindre ${companyName} sur RentalDriveGo en tant que ${role.toLowerCase()}.</p>
<p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">Definir votre mot de passe</a></p>
<p>Ce lien d'invitation expire dans 7 jours.</p>
<p>RentalDriveGo</p>
`,
text: `Bonjour ${greetingName},\n\nVous avez ete invite a rejoindre ${companyName} sur RentalDriveGo en tant que ${role.toLowerCase()}.\n\nDefinissez votre mot de passe ici : ${resetUrl}\n\nCe lien d'invitation expire dans 7 jours.\n\nRentalDriveGo`,
};
}
if (locale === 'ar') {
return {
subject: `تمت دعوتك إلى ${companyName}`,
html: `
<div dir="rtl" style="text-align:right">
<p>مرحباً ${greetingName}،</p>
<p>تمت دعوتك للانضمام إلى ${companyName} على RentalDriveGo بصفتك ${role.toLowerCase()}.</p>
<p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">عيّن كلمة المرور</a></p>
<p>تنتهي صلاحية رابط الدعوة هذا خلال 7 أيام.</p>
<p>RentalDriveGo</p>
</div>
`,
text: `مرحباً ${greetingName}،\n\nتمت دعوتك للانضمام إلى ${companyName} على RentalDriveGo بصفتك ${role.toLowerCase()}.\n\nعيّن كلمة المرور هنا: ${resetUrl}\n\nتنتهي صلاحية رابط الدعوة هذا خلال 7 أيام.\n\nRentalDriveGo`,
};
}
return {
subject: `You have been invited to ${companyName}`,
html: `
<p>Hi ${greetingName},</p>
<p>You were invited to join ${companyName} on RentalDriveGo as a ${role.toLowerCase()}.</p>
<p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">Set your password</a></p>
<p>This invitation link expires in 7 days.</p>
<p>RentalDriveGo</p>
`,
text: `Hi ${greetingName},\n\nYou were invited to join ${companyName} on RentalDriveGo as a ${role.toLowerCase()}.\n\nSet your password here: ${resetUrl}\n\nThis invitation link expires in 7 days.\n\nRentalDriveGo`,
};
}
async function listEmployees(companyId) {
const employees = await prisma_1.prisma.employee.findMany({
where: { companyId },
orderBy: [{ role: 'asc' }, { createdAt: 'asc' }],
});
return employees.map((employee) => ({
...employee,
lastActiveAt: null,
invitationStatus: employee.passwordHash ? 'accepted' : 'pending',
}));
}
async function inviteEmployee(companyId, inviterId, payload) {
if (payload.role === 'OWNER') {
throw Object.assign(new Error('Cannot invite a member with the OWNER role'), { statusCode: 400, code: 'invalid_role' });
}
const existing = await prisma_1.prisma.employee.findFirst({ where: { companyId, email: payload.email } });
if (existing) {
throw Object.assign(new Error('An employee with this email already exists in your team'), { statusCode: 409, code: 'employee_already_exists' });
}
const company = await prisma_1.prisma.company.findUniqueOrThrow({
where: { id: companyId },
include: { brand: { select: { defaultLocale: true, displayName: true } } },
});
const rawToken = crypto_1.default.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + INVITE_TOKEN_TTL_MINUTES * 60 * 1000);
const locale = (0, notificationLocalizationService_1.coerceNotificationLocale)(company.brand?.defaultLocale);
const employee = await prisma_1.prisma.employee.create({
data: {
companyId,
clerkUserId: `local_member_${crypto_1.default.randomUUID()}`,
firstName: payload.firstName,
lastName: payload.lastName,
email: payload.email,
role: payload.role,
isActive: true,
preferredLanguage: locale,
passwordResetToken: rawToken,
passwordResetExpiresAt: expiresAt,
},
});
const dashboardUrl = ensureDashboardBasePath(process.env.DASHBOARD_URL ?? process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard');
const resetUrl = `${dashboardUrl}/reset-password?token=${rawToken}`;
const message = buildInviteEmail(resetUrl, company.brand?.displayName ?? company.name, payload.firstName, payload.role, locale);
await (0, notificationService_1.sendTransactionalEmail)({
to: payload.email,
subject: message.subject,
html: message.html,
text: message.text,
});
return {
employee: {
...employee,
lastActiveAt: null,
invitationStatus: 'pending',
},
invitationId: employee.id,
invitedBy: inviterId,
};
}
async function updateEmployeeRole(companyId, requesterId, requesterRole, employeeId, payload) {
const target = await prisma_1.prisma.employee.findFirstOrThrow({ where: { id: employeeId, companyId } });
if (requesterRole !== 'OWNER')
throw Object.assign(new Error('Only the account owner can change team member roles'), { statusCode: 403, code: 'forbidden' });
if (target.role === 'OWNER')
throw Object.assign(new Error('Cannot change the role of the account owner'), { statusCode: 400 });
if (payload.role === 'OWNER')
throw Object.assign(new Error('Cannot assign the OWNER role via this endpoint'), { statusCode: 400 });
if (target.id === requesterId)
throw Object.assign(new Error('You cannot change your own role'), { statusCode: 400 });
return prisma_1.prisma.employee.update({ where: { id: employeeId }, data: { role: payload.role } });
}
async function deactivateEmployee(companyId, requesterRole, employeeId) {
if (requesterRole !== 'OWNER')
throw Object.assign(new Error('Only the account owner can deactivate team members'), { statusCode: 403 });
const target = await prisma_1.prisma.employee.findFirstOrThrow({ where: { id: employeeId, companyId } });
if (target.role === 'OWNER')
throw Object.assign(new Error('Cannot deactivate the account owner'), { statusCode: 400 });
return prisma_1.prisma.employee.update({ where: { id: employeeId }, data: { isActive: false } });
}
async function reactivateEmployee(companyId, requesterRole, employeeId) {
if (requesterRole !== 'OWNER')
throw Object.assign(new Error('Only the account owner can reactivate team members'), { statusCode: 403 });
await prisma_1.prisma.employee.findFirstOrThrow({ where: { id: employeeId, companyId } });
return prisma_1.prisma.employee.update({ where: { id: employeeId }, data: { isActive: true } });
}
async function removeEmployee(companyId, requesterRole, employeeId) {
if (requesterRole !== 'OWNER')
throw Object.assign(new Error('Only the account owner can remove team members'), { statusCode: 403 });
const target = await prisma_1.prisma.employee.findFirstOrThrow({ where: { id: employeeId, companyId } });
if (target.role === 'OWNER')
throw Object.assign(new Error('Cannot remove the account owner'), { statusCode: 400 });
await prisma_1.prisma.employee.delete({ where: { id: employeeId } });
return { success: true };
}
//# sourceMappingURL=teamService.js.map