feat: add community publishing and news syndication
This commit is contained in:
+61
-38
@@ -4,55 +4,78 @@ import nodemailer from 'nodemailer';
|
||||
let transporter: ReturnType<typeof nodemailer.createTransport> | undefined;
|
||||
|
||||
function mailer() {
|
||||
if (!env.SMTP_HOST || !env.SMTP_FROM) {
|
||||
throw new Error('Email delivery is not configured.');
|
||||
}
|
||||
if (!transporter) {
|
||||
transporter = nodemailer.createTransport({
|
||||
host: env.SMTP_HOST,
|
||||
port: Number(env.SMTP_PORT || 587),
|
||||
secure: env.SMTP_SECURE === 'true',
|
||||
auth: env.SMTP_USER ? { user: env.SMTP_USER, pass: env.SMTP_PASSWORD } : undefined,
|
||||
requireTLS: env.SMTP_SECURE !== 'true',
|
||||
disableFileAccess: true,
|
||||
disableUrlAccess: true
|
||||
});
|
||||
}
|
||||
return transporter;
|
||||
if (!env.SMTP_HOST || !env.SMTP_FROM) {
|
||||
throw new Error('Email delivery is not configured.');
|
||||
}
|
||||
if (!transporter) {
|
||||
transporter = nodemailer.createTransport({
|
||||
host: env.SMTP_HOST,
|
||||
port: Number(env.SMTP_PORT || 587),
|
||||
secure: env.SMTP_SECURE === 'true',
|
||||
auth: env.SMTP_USER
|
||||
? { user: env.SMTP_USER, pass: env.SMTP_PASSWORD }
|
||||
: undefined,
|
||||
requireTLS: env.SMTP_SECURE !== 'true',
|
||||
disableFileAccess: true,
|
||||
disableUrlAccess: true,
|
||||
});
|
||||
}
|
||||
return transporter;
|
||||
}
|
||||
|
||||
export function applicationOrigin(): string {
|
||||
return (env.APP_ORIGIN || 'http://localhost:5173').replace(/\/+$/, '');
|
||||
return (env.APP_ORIGIN || 'http://localhost:5173').replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
export function emailDeliveryConfigured(): boolean {
|
||||
return Boolean(env.SMTP_HOST && env.SMTP_FROM);
|
||||
return Boolean(env.SMTP_HOST && env.SMTP_FROM);
|
||||
}
|
||||
|
||||
export async function sendAuthenticationLink(email: string, token: string, purpose: 'login' | 'verify_email'): Promise<void> {
|
||||
const url = `${applicationOrigin()}/auth/magic-link?token=${encodeURIComponent(token)}`;
|
||||
const subject = purpose === 'login' ? 'Sign in to SHD.build' : 'Verify your SHD.build email';
|
||||
const action = purpose === 'login' ? 'SIGN IN TO SHD.BUILD' : 'VERIFY EMAIL ADDRESS';
|
||||
await mailer().sendMail({
|
||||
from: env.SMTP_FROM,
|
||||
to: email,
|
||||
subject,
|
||||
text: `${subject}\n\nOpen this one-time link within 15 minutes:\n${url}\n\nIf you did not request this, you can ignore this message.`,
|
||||
html: `<div style="background:#0a0c0d;color:#ecebe5;padding:28px;font-family:Arial,sans-serif"><p style="color:#f6a821;font-size:12px;letter-spacing:2px">SHD IDENTITY NETWORK</p><h1 style="font-size:24px">${subject}</h1><p style="color:#a5aaab">This one-time link expires in 15 minutes.</p><p><a href="${url}" style="display:inline-block;background:#f6a821;color:#111;padding:14px 20px;text-decoration:none;font-weight:800">${action}</a></p><p style="color:#737a7c;font-size:12px">If you did not request this, you can ignore this message.</p></div>`
|
||||
});
|
||||
export async function sendAuthenticationLink(
|
||||
email: string,
|
||||
token: string,
|
||||
purpose: 'login' | 'verify_email',
|
||||
): Promise<void> {
|
||||
const url = `${applicationOrigin()}/auth/magic-link?token=${encodeURIComponent(token)}`;
|
||||
const subject =
|
||||
purpose === 'login'
|
||||
? 'Sign in to SHD.build'
|
||||
: 'Verify your SHD.build email';
|
||||
const action =
|
||||
purpose === 'login' ? 'SIGN IN TO SHD.BUILD' : 'VERIFY EMAIL ADDRESS';
|
||||
await mailer().sendMail({
|
||||
from: env.SMTP_FROM,
|
||||
to: email,
|
||||
subject,
|
||||
text: `${subject}\n\nOpen this one-time link within 15 minutes:\n${url}\n\nIf you did not request this, you can ignore this message.`,
|
||||
html: `<div style="background:#0a0c0d;color:#ecebe5;padding:28px;font-family:Arial,sans-serif"><p style="color:#f6a821;font-size:12px;letter-spacing:2px">SHD IDENTITY NETWORK</p><h1 style="font-size:24px">${subject}</h1><p style="color:#a5aaab">This one-time link expires in 15 minutes.</p><p><a href="${url}" style="display:inline-block;background:#f6a821;color:#111;padding:14px 20px;text-decoration:none;font-weight:800">${action}</a></p><p style="color:#737a7c;font-size:12px">If you did not request this, you can ignore this message.</p></div>`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendNotificationEmail(email: string, title: string, body: string, link: string | null): Promise<void> {
|
||||
const url = link ? `${applicationOrigin()}${link.startsWith('/') ? link : `/${link}`}` : `${applicationOrigin()}/account/notifications`;
|
||||
await mailer().sendMail({
|
||||
from: env.SMTP_FROM,
|
||||
to: email,
|
||||
subject: `${title} — SHD.build`,
|
||||
text: `${title}\n\n${body}\n\nOpen SHD.build: ${url}`,
|
||||
html: `<div style="background:#0a0c0d;color:#ecebe5;padding:28px;font-family:Arial,sans-serif"><p style="color:#f6a821;font-size:12px;letter-spacing:2px">SHD NOTIFICATION NETWORK</p><h1 style="font-size:24px">${escapeHtml(title)}</h1><p style="color:#a5aaab;line-height:1.6">${escapeHtml(body)}</p><p><a href="${url}" style="display:inline-block;background:#f6a821;color:#111;padding:14px 20px;text-decoration:none;font-weight:800">OPEN SHD INBOX</a></p></div>`
|
||||
});
|
||||
export async function sendNotificationEmail(
|
||||
email: string,
|
||||
title: string,
|
||||
body: string,
|
||||
link: string | null,
|
||||
): Promise<void> {
|
||||
const url = link
|
||||
? `${applicationOrigin()}${link.startsWith('/') ? link : `/${link}`}`
|
||||
: `${applicationOrigin()}/account/notifications`;
|
||||
await mailer().sendMail({
|
||||
from: env.SMTP_FROM,
|
||||
to: email,
|
||||
subject: `${title} — SHD.build`,
|
||||
text: `${title}\n\n${body}\n\nOpen SHD.build: ${url}`,
|
||||
html: `<div style="background:#0a0c0d;color:#ecebe5;padding:28px;font-family:Arial,sans-serif"><p style="color:#f6a821;font-size:12px;letter-spacing:2px">SHD NOTIFICATION NETWORK</p><h1 style="font-size:24px">${escapeHtml(title)}</h1><p style="color:#a5aaab;line-height:1.6">${escapeHtml(body)}</p><p><a href="${url}" style="display:inline-block;background:#f6a821;color:#111;padding:14px 20px;text-decoration:none;font-weight:800">OPEN SHD INBOX</a></p></div>`,
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(value: string) {
|
||||
return value.replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character]!);
|
||||
return value.replace(
|
||||
/[&<>"']/g,
|
||||
(character) =>
|
||||
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[
|
||||
character
|
||||
]!,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user