added admin email notification
This commit is contained in:
@@ -6,3 +6,4 @@ BACKEND_URL=http://localhost:5000
|
|||||||
VITE_API_BASE_URL=http://localhost:5000/api
|
VITE_API_BASE_URL=http://localhost:5000/api
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
ADMIN_EMAILS=corey@blaishome.online
|
ADMIN_EMAILS=corey@blaishome.online
|
||||||
|
RESCUE_STATUS_NOTIFICATION_EMAIL=appadmin@flockpal.app
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ const smtpUser = process.env.SMTP_USER?.trim() ?? '';
|
|||||||
const smtpPass = process.env.SMTP_PASS?.trim() ?? '';
|
const smtpPass = process.env.SMTP_PASS?.trim() ?? '';
|
||||||
const smtpFromEmail = process.env.SMTP_FROM_EMAIL?.trim() ?? '';
|
const smtpFromEmail = process.env.SMTP_FROM_EMAIL?.trim() ?? '';
|
||||||
const smtpFromName = process.env.SMTP_FROM_NAME?.trim() || 'FlockPal';
|
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(
|
const adminEmails = new Set(
|
||||||
(process.env.ADMIN_EMAILS ?? '')
|
(process.env.ADMIN_EMAILS ?? '')
|
||||||
.split(',')
|
.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 ({
|
const issueMagicLinkInvite = async ({
|
||||||
email,
|
email,
|
||||||
name,
|
name,
|
||||||
@@ -1006,6 +1065,12 @@ app.patch('/api/admin/rescue-workspaces/:workspaceId', requireAuth, requireSessi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await sendRescueStatusNotification({
|
||||||
|
workspace,
|
||||||
|
ownerEmail: null,
|
||||||
|
event: 'status_changed',
|
||||||
|
});
|
||||||
|
|
||||||
res.json({ workspace: normalizeWorkspace(workspace) });
|
res.json({ workspace: normalizeWorkspace(workspace) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -1098,6 +1163,14 @@ app.post('/api/workspaces', requireAuth, requireSessionAuth, async (req: Request
|
|||||||
owner: req.auth!.user,
|
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!) });
|
res.status(201).json({ workspace: normalizeWorkspace(workspace!) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -1126,6 +1199,14 @@ app.put('/api/workspace', requireAuth, requireWriteAccess, requireWorkspaceRole(
|
|||||||
billingPlan,
|
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!) });
|
res.json({ workspace: normalizeWorkspace(workspace!) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ services:
|
|||||||
FRONTEND_URL: ${FRONTEND_URL:?set FRONTEND_URL for production}
|
FRONTEND_URL: ${FRONTEND_URL:?set FRONTEND_URL for production}
|
||||||
BACKEND_URL: ${BACKEND_URL:?set BACKEND_URL for production}
|
BACKEND_URL: ${BACKEND_URL:?set BACKEND_URL for production}
|
||||||
ADMIN_EMAILS: ${ADMIN_EMAILS:-}
|
ADMIN_EMAILS: ${ADMIN_EMAILS:-}
|
||||||
|
RESCUE_STATUS_NOTIFICATION_EMAIL: ${RESCUE_STATUS_NOTIFICATION_EMAIL:-appadmin@flockpal.app}
|
||||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
||||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
||||||
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
|
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ services:
|
|||||||
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
|
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
|
||||||
BACKEND_URL: ${BACKEND_URL:-http://localhost:5000}
|
BACKEND_URL: ${BACKEND_URL:-http://localhost:5000}
|
||||||
ADMIN_EMAILS: ${ADMIN_EMAILS:-}
|
ADMIN_EMAILS: ${ADMIN_EMAILS:-}
|
||||||
|
RESCUE_STATUS_NOTIFICATION_EMAIL: ${RESCUE_STATUS_NOTIFICATION_EMAIL:-appadmin@flockpal.app}
|
||||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
||||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
||||||
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
|
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
|
||||||
|
|||||||
Reference in New Issue
Block a user