Phase 1-14 complete: - Unified Express.js API (TypeScript, Prisma ORM, PostgreSQL 16) - React Admin GUI (Vite + Ant Design + Zustand) - JWT auth with refresh tokens - Influence: Campaigns, Representatives, Responses, Email Queue - Map: Locations, Cuts, Shifts, Canvassing System - NAR data import infrastructure (2025 format) - Listmonk newsletter integration - Landing page builder (GrapesJS) - MkDocs + Code Server integration - Volunteer portal with GPS tracking - Monitoring stack (Prometheus, Grafana, Alertmanager) - Pangolin tunnel integration Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
import { Router, Request, Response, NextFunction } from 'express';
|
|
import { UserRole } from '@prisma/client';
|
|
import { siteSettingsService } from './settings.service';
|
|
import { updateSiteSettingsSchema } from './settings.schemas';
|
|
import { validate } from '../../middleware/validate';
|
|
import { authenticate } from '../../middleware/auth.middleware';
|
|
import { requireRole } from '../../middleware/rbac.middleware';
|
|
import { emailService } from '../../services/email.service';
|
|
|
|
const router = Router();
|
|
|
|
// GET /api/settings — public (needed by login page + public pages), strips SMTP credentials
|
|
router.get(
|
|
'/',
|
|
async (_req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const settings = await siteSettingsService.getPublic();
|
|
res.json(settings);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// GET /api/settings/admin — SUPER_ADMIN only, returns full settings including SMTP
|
|
router.get(
|
|
'/admin',
|
|
authenticate,
|
|
requireRole(UserRole.SUPER_ADMIN),
|
|
async (_req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const settings = await siteSettingsService.get();
|
|
res.json(settings);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// POST /api/settings/email/test-connection — SUPER_ADMIN only
|
|
router.post(
|
|
'/email/test-connection',
|
|
authenticate,
|
|
requireRole(UserRole.SUPER_ADMIN),
|
|
async (_req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const success = await emailService.testConnection();
|
|
res.json({ success, message: success ? 'SMTP connection verified' : 'SMTP connection failed' });
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// POST /api/settings/email/test-send — SUPER_ADMIN only
|
|
router.post(
|
|
'/email/test-send',
|
|
authenticate,
|
|
requireRole(UserRole.SUPER_ADMIN),
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { to } = req.body as { to?: string };
|
|
const settings = await siteSettingsService.get();
|
|
const recipient = to || settings.testEmailRecipient || 'admin@cmlite.org';
|
|
|
|
const result = await emailService.sendEmail({
|
|
to: recipient,
|
|
subject: 'Changemaker Lite — Test Email',
|
|
html: `<h2>SMTP Test Successful</h2><p>This email confirms that your SMTP configuration is working correctly.</p><p>Sent at: ${new Date().toISOString()}</p>`,
|
|
text: `SMTP Test Successful\n\nThis email confirms that your SMTP configuration is working correctly.\n\nSent at: ${new Date().toISOString()}`,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
messageId: result.messageId,
|
|
testMode: result.testMode,
|
|
recipient,
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// PUT /api/settings — SUPER_ADMIN only
|
|
router.put(
|
|
'/',
|
|
authenticate,
|
|
requireRole(UserRole.SUPER_ADMIN),
|
|
validate(updateSiteSettingsSchema),
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const settings = await siteSettingsService.update(req.body);
|
|
|
|
// If SMTP-related fields were updated, rebuild the transporter
|
|
const smtpFields = ['smtpHost', 'smtpPort', 'smtpUser', 'smtpPass', 'smtpFromAddress', 'smtpActiveProvider', 'emailTestMode', 'testEmailRecipient'];
|
|
const hasSmtpChanges = smtpFields.some((f) => f in req.body);
|
|
if (hasSmtpChanges) {
|
|
await emailService.rebuildTransporter();
|
|
}
|
|
|
|
res.json(settings);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
export { router as siteSettingsRouter };
|