|
|
|
@@ -219,6 +219,7 @@ const smtpUser = process.env.SMTP_USER?.trim() ?? '';
|
|
|
|
|
const smtpPass = process.env.SMTP_PASS?.trim() ?? '';
|
|
|
|
|
const smtpFromEmail = process.env.SMTP_FROM_EMAIL?.trim() ?? '';
|
|
|
|
|
const smtpFromName = process.env.SMTP_FROM_NAME?.trim() || 'FlockPal';
|
|
|
|
|
const rescueStatusNotificationEmail = process.env.RESCUE_STATUS_NOTIFICATION_EMAIL?.trim() || 'appadmin@flockpal.app';
|
|
|
|
|
const adminEmails = new Set(
|
|
|
|
|
(process.env.ADMIN_EMAILS ?? '')
|
|
|
|
|
.split(',')
|
|
|
|
@@ -487,6 +488,64 @@ const sendMagicLink = async ({
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const escapeHtml = (value: string) =>
|
|
|
|
|
value
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
.replace(/'/g, ''');
|
|
|
|
|
|
|
|
|
|
const sendRescueStatusNotification = async ({
|
|
|
|
|
workspace,
|
|
|
|
|
ownerEmail,
|
|
|
|
|
event,
|
|
|
|
|
}: {
|
|
|
|
|
workspace: WorkspaceRow;
|
|
|
|
|
ownerEmail: string | null;
|
|
|
|
|
event: 'created' | 'converted' | 'status_changed';
|
|
|
|
|
}) => {
|
|
|
|
|
const statusLabel = workspace.rescue_verification_status.replace(/_/g, ' ');
|
|
|
|
|
const eventLabel = event === 'created' ? 'created' : event === 'converted' ? 'converted to rescue' : 'status updated';
|
|
|
|
|
const subject = `FlockPal rescue status: ${workspace.name} ${eventLabel}`;
|
|
|
|
|
const escapedWorkspaceName = escapeHtml(workspace.name);
|
|
|
|
|
const escapedStatusLabel = escapeHtml(statusLabel);
|
|
|
|
|
const escapedOwnerEmail = escapeHtml(ownerEmail ?? 'unknown');
|
|
|
|
|
const escapedBillingEmail = escapeHtml(workspace.billing_email ?? 'not set');
|
|
|
|
|
const lines = [
|
|
|
|
|
`Rescue flock: ${workspace.name}`,
|
|
|
|
|
`Event: ${eventLabel}`,
|
|
|
|
|
`Verification status: ${statusLabel}`,
|
|
|
|
|
`Owner email: ${ownerEmail ?? 'unknown'}`,
|
|
|
|
|
`Billing email: ${workspace.billing_email ?? 'not set'}`,
|
|
|
|
|
`Flock ID: ${workspace.id}`,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (!mailTransport) {
|
|
|
|
|
console.log(`Rescue status notification for ${rescueStatusNotificationEmail}:\n${lines.join('\n')}`);
|
|
|
|
|
return { delivered: false };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await mailTransport.sendMail({
|
|
|
|
|
from: smtpFromName ? `"${smtpFromName}" <${smtpFromEmail}>` : smtpFromEmail,
|
|
|
|
|
to: rescueStatusNotificationEmail,
|
|
|
|
|
subject,
|
|
|
|
|
text: lines.join('\n'),
|
|
|
|
|
html: `
|
|
|
|
|
<p>A rescue flock was ${eventLabel}.</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><strong>Rescue flock:</strong> ${escapedWorkspaceName}</li>
|
|
|
|
|
<li><strong>Verification status:</strong> ${escapedStatusLabel}</li>
|
|
|
|
|
<li><strong>Owner email:</strong> ${escapedOwnerEmail}</li>
|
|
|
|
|
<li><strong>Billing email:</strong> ${escapedBillingEmail}</li>
|
|
|
|
|
<li><strong>Flock ID:</strong> ${workspace.id}</li>
|
|
|
|
|
</ul>
|
|
|
|
|
`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { delivered: true };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const issueMagicLinkInvite = async ({
|
|
|
|
|
email,
|
|
|
|
|
name,
|
|
|
|
@@ -1006,6 +1065,12 @@ app.patch('/api/admin/rescue-workspaces/:workspaceId', requireAuth, requireSessi
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await sendRescueStatusNotification({
|
|
|
|
|
workspace,
|
|
|
|
|
ownerEmail: null,
|
|
|
|
|
event: 'status_changed',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({ workspace: normalizeWorkspace(workspace) });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
@@ -1098,6 +1163,14 @@ app.post('/api/workspaces', requireAuth, requireSessionAuth, async (req: Request
|
|
|
|
|
owner: req.auth!.user,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (workspace?.workspace_type === 'rescue') {
|
|
|
|
|
await sendRescueStatusNotification({
|
|
|
|
|
workspace,
|
|
|
|
|
ownerEmail: req.auth!.user.email,
|
|
|
|
|
event: 'created',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.status(201).json({ workspace: normalizeWorkspace(workspace!) });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
@@ -1126,6 +1199,14 @@ app.put('/api/workspace', requireAuth, requireWriteAccess, requireWorkspaceRole(
|
|
|
|
|
billingPlan,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (workspace?.workspace_type === 'rescue' && req.auth!.workspace.workspace_type !== 'rescue') {
|
|
|
|
|
await sendRescueStatusNotification({
|
|
|
|
|
workspace,
|
|
|
|
|
ownerEmail: req.auth!.user.email,
|
|
|
|
|
event: 'converted',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({ workspace: normalizeWorkspace(workspace!) });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
|