Add internal registry endpoint, self-registration, and workforce sync
- Register internalRoutes at /api/auth/internal (used by management) - Add registerRoutes for staff self-registration flow - Add workforce sync job (runs on schedule, fails gracefully if unconfigured) - Add email.js for registration/invite emails Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2d89fd9227
commit
f08d53d702
10 changed files with 704 additions and 18 deletions
61
src/email.js
Normal file
61
src/email.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import nodemailer from 'nodemailer'
|
||||
|
||||
const SETTINGS_URL = process.env.SETTINGS_URL || 'http://10.10.10.106:3080'
|
||||
const SETTINGS_SECRET = process.env.SETTINGS_SECRET || ''
|
||||
|
||||
let _smtpCache = null // { config, expires_at }
|
||||
let _transporter = null
|
||||
|
||||
async function getSmtpConfig() {
|
||||
if (_smtpCache && Date.now() < _smtpCache.expires_at) return _smtpCache.config
|
||||
const res = await fetch(`${SETTINGS_URL}/settings/api/internal/integration/smtp`, {
|
||||
headers: { Authorization: `Bearer ${SETTINGS_SECRET}` },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
})
|
||||
if (!res.ok) throw new Error(`Failed to fetch SMTP config from settings: ${res.status}`)
|
||||
const config = await res.json()
|
||||
if (!config.host) throw new Error('SMTP not configured in settings')
|
||||
_smtpCache = { config, expires_at: Date.now() + 5 * 60_000 }
|
||||
_transporter = null // force transporter rebuild on next send
|
||||
return config
|
||||
}
|
||||
|
||||
async function getTransporter() {
|
||||
if (_transporter) return _transporter
|
||||
const config = await getSmtpConfig()
|
||||
const port = parseInt(config.port || '587')
|
||||
_transporter = nodemailer.createTransport({
|
||||
host: config.host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
auth: config.user ? { user: config.user, pass: config.pass } : undefined,
|
||||
})
|
||||
return _transporter
|
||||
}
|
||||
|
||||
export async function sendPinEmail(to, name, pin) {
|
||||
const config = await getSmtpConfig()
|
||||
const transport = await getTransporter()
|
||||
const from = config.from || '"Hotel Number Four" <noreply@hotelnumberfour.com>'
|
||||
|
||||
await transport.sendMail({
|
||||
from,
|
||||
to,
|
||||
subject: 'Your Hotel Number Four verification code',
|
||||
text: `Hi ${name},\n\nYour staff portal verification code is: ${pin}\n\nThis code expires in 15 minutes.\n\nIf you did not request this, please ignore this email.`,
|
||||
html: `
|
||||
<div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:2rem">
|
||||
<h2 style="color:#1e3a5f;margin:0 0 1rem">Hotel Number Four</h2>
|
||||
<p>Hi ${name},</p>
|
||||
<p>Your staff portal verification code is:</p>
|
||||
<div style="font-size:2.5rem;font-weight:700;letter-spacing:0.3em;color:#1e3a5f;
|
||||
background:#f3f4f6;padding:1rem 2rem;border-radius:8px;
|
||||
text-align:center;margin:1.5rem 0">${pin}</div>
|
||||
<p style="color:#6b7280;font-size:0.875rem">
|
||||
This code expires in 15 minutes.<br>
|
||||
If you did not request access to the staff portal, please ignore this email.
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue