diff --git a/package.json b/package.json index cce3169..d79806f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.0.6", + "version": "2.0.7", "type": "module", "scripts": { "dev": "vite", diff --git a/server/email.js b/server/email.js index 7e67f73..e320d9f 100644 --- a/server/email.js +++ b/server/email.js @@ -764,3 +764,44 @@ export async function sendNuggetsUploadNotification({ to, source, hostName, epis console.error('[nuggets-notify] send exception:', err) } } + +export async function sendNuggetsHostWelcomeEmail({ to, name, username, password, portalUrl }) { + if (!process.env.RESEND_API_KEY || !to) return + const resend = new Resend(process.env.RESEND_API_KEY) + const from = process.env.RESEND_FROM ?? DEFAULT_RESEND_FROM + + const bodyHtml = ` +
Hi ${escapeHtml(name)},
++ You've been set up as a host on the Nuggets episode upload portal. + Use the details below to sign in and submit your episodes. +
+| Portal URL | ${escapeHtml(portalUrl)} |
| Username | ${escapeHtml(username)} |
| Password | ${escapeHtml(password)} |
When you're ready to submit an episode, fill in the episode name, the Bible passage, a short description, and attach your audio file. Everything uploads in one step.
+If you have any trouble signing in, reply to this email and we'll get you sorted.
+ ` + + try { + const { error } = await resend.emails.send({ + from, + to: [to], + subject: 'Your Nuggets Host Upload Portal Access', + text: `Hi ${name},\n\nYou've been set up as a host on the Nuggets episode upload portal.\n\nPortal URL: ${portalUrl}\nUsername: ${username}\nPassword: ${password}\n\nWhen submitting an episode, fill in the episode name, Bible passage, a short description, and attach your audio file.\n\nIf you have any trouble, reply to this email.`, + html: buildBrandedEmailHtml({ + title: 'Upload Portal Access', + eyebrow: 'Nuggets', + bodyHtml, + ctaLabel: 'Open Upload Portal', + ctaUrl: portalUrl, + footerHtml: 'You received this because an admin created an upload account for you on the Nuggets podcast portal.
', + }), + }) + if (error) console.error('[nuggets-welcome] send error:', error) + } catch (err) { + console.error('[nuggets-welcome] send exception:', err) + } +} diff --git a/server/routes/nuggets-host-upload.js b/server/routes/nuggets-host-upload.js index fb642b1..e6423e5 100644 --- a/server/routes/nuggets-host-upload.js +++ b/server/routes/nuggets-host-upload.js @@ -6,7 +6,7 @@ import rateLimit from 'express-rate-limit' import { createHash, timingSafeEqual } from 'node:crypto' import { parseCookies, cookieSecureFlag } from '../helpers.js' import { requireAdminAuth } from '../auth.js' -import { sendNuggetsUploadNotification } from '../email.js' +import { sendNuggetsUploadNotification, sendNuggetsHostWelcomeEmail } from '../email.js' import { markSeafileFileSeen } from '../nuggets-seafile-watcher.js' import { NUGGETS_UPLOAD_HOSTS_FILE, @@ -281,6 +281,7 @@ export function register(app) { const name = typeof req.body?.name === 'string' ? req.body.name.trim() : '' const username = typeof req.body?.username === 'string' ? req.body.username.trim().toLowerCase() : '' const password = typeof req.body?.password === 'string' ? req.body.password : '' + const email = typeof req.body?.email === 'string' ? req.body.email.trim().toLowerCase() : '' if (!name || !username || !password) { res.status(400).json({ message: 'Name, username, and password are required.' }) @@ -301,6 +302,7 @@ export function register(app) { id: randomUUID(), name, username, + email, passwordHash: hashPassword(password), active: true, createdAt: new Date().toISOString(), @@ -308,8 +310,19 @@ export function register(app) { data.hosts.push(host) await saveUploadHosts(data) + // Send welcome email if an address was provided + if (email) { + sendNuggetsHostWelcomeEmail({ + to: email, + name, + username, + password, + portalUrl: `${process.env.SITE_URL ?? 'https://versebyversewithnate.us'}/host`, + }).catch(err => console.error('[nuggets-host] welcome email error:', err)) + } + const { passwordHash: _ph, ...safeHost } = host - res.json({ ok: true, host: safeHost }) + res.json({ ok: true, host: safeHost, emailSent: Boolean(email) }) }) app.put('/api/admin/nuggets-upload/hosts/:id', requireAdminAuth, async (req, res) => { @@ -332,11 +345,23 @@ export function register(app) { patch.passwordHash = hashPassword(req.body.password) } + const newPassword = typeof req.body?.password === 'string' && req.body.password.length > 0 ? req.body.password : null data.hosts[idx] = { ...data.hosts[idx], ...patch } await saveUploadHosts(data) + // Email the host their new password if they have an address on file + if (newPassword && data.hosts[idx].email) { + sendNuggetsHostWelcomeEmail({ + to: data.hosts[idx].email, + name: data.hosts[idx].name, + username: data.hosts[idx].username, + password: newPassword, + portalUrl: `${process.env.SITE_URL ?? 'https://versebyversewithnate.us'}/host`, + }).catch(err => console.error('[nuggets-host] reset email error:', err)) + } + const { passwordHash: _ph, ...safeHost } = data.hosts[idx] - res.json({ ok: true, host: safeHost }) + res.json({ ok: true, host: safeHost, emailSent: Boolean(newPassword && data.hosts[idx].email) }) }) app.delete('/api/admin/nuggets-upload/hosts/:id', requireAdminAuth, async (req, res) => { diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx index 9a23c76..a8028ed 100644 --- a/src/AdminPage.tsx +++ b/src/AdminPage.tsx @@ -1101,6 +1101,7 @@ interface UploadHost { id: string name: string username: string + email: string active: boolean createdAt: string } @@ -1111,13 +1112,16 @@ function NuggetsUploadPortalAdmin() { const [error, setError] = useState('') const [addName, setAddName] = useState('') const [addUsername, setAddUsername] = useState('') + const [addEmail, setAddEmail] = useState('') const [addPassword, setAddPassword] = useState('') const [addError, setAddError] = useState('') const [addSaving, setAddSaving] = useState(false) + const [addEmailSent, setAddEmailSent] = useState(false) const [resetId, setResetId] = useStateManage host login accounts for the upload portal at{' '} - /host-upload. + /host. Each host gets a username and password to upload episode files to Seafile.
@@ -1207,6 +1213,7 @@ function NuggetsUploadPortalAdmin() {
{resetError}
} )} + {resetEmailSent && !resetId &&✓ New password emailed to host.
}