mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +00:00
feat: email notifications, webhook support, ICS export — closes #110
Email Notifications: - SMTP configuration in Admin > Settings (host, port, user, pass, from) - App URL setting for email CTA links - Webhook URL support (Discord, Slack, custom) - Test email button with SMTP validation - Beautiful HTML email template with TREK logo, slogan, red heart footer - All notification texts translated in 8 languages (en/de/fr/es/nl/ru/zh/ar) - Emails sent in each user's language preference Notification Events: - Trip invitation (member added) - Booking created (new reservation) - Vacay fusion invite - Photos shared (Immich) - Collab chat message - Packing list category assignment User Notification Preferences: - Per-user toggle for each event type in Settings - Addon-aware: Vacay/Collab/Photos toggles hidden when addon disabled - Webhook opt-in per user ICS Calendar Export: - Download button next to PDF in day plan header - Exports trip dates + all reservations with details - Compatible with Google Calendar, Apple Calendar, Outlook Technical: - Nodemailer for SMTP - notification_preferences DB table with per-event columns - GET/PUT /auth/app-settings for admin config persistence - POST /notifications/test-smtp for validation - Dynamic imports for non-blocking notification sends
This commit is contained in:
@@ -515,17 +515,33 @@ router.get('/validate-keys', authenticate, async (req: Request, res: Response) =
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
const ADMIN_SETTINGS_KEYS = ['allow_registration', 'allowed_file_types', 'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_from', 'notification_webhook_url', 'app_url'];
|
||||
|
||||
router.get('/app-settings', authenticate, (req: Request, res: Response) => {
|
||||
const authReq = req as AuthRequest;
|
||||
const user = db.prepare('SELECT role FROM users WHERE id = ?').get(authReq.user.id) as { role: string } | undefined;
|
||||
if (user?.role !== 'admin') return res.status(403).json({ error: 'Admin access required' });
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
for (const key of ADMIN_SETTINGS_KEYS) {
|
||||
const row = db.prepare("SELECT value FROM app_settings WHERE key = ?").get(key) as { value: string } | undefined;
|
||||
if (row) result[key] = key === 'smtp_pass' ? '••••••••' : row.value;
|
||||
}
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
router.put('/app-settings', authenticate, (req: Request, res: Response) => {
|
||||
const authReq = req as AuthRequest;
|
||||
const user = db.prepare('SELECT role FROM users WHERE id = ?').get(authReq.user.id) as { role: string } | undefined;
|
||||
if (user?.role !== 'admin') return res.status(403).json({ error: 'Admin access required' });
|
||||
|
||||
const { allow_registration, allowed_file_types } = req.body;
|
||||
if (allow_registration !== undefined) {
|
||||
db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allow_registration', ?)").run(String(allow_registration));
|
||||
}
|
||||
if (allowed_file_types !== undefined) {
|
||||
db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allowed_file_types', ?)").run(String(allowed_file_types));
|
||||
for (const key of ADMIN_SETTINGS_KEYS) {
|
||||
if (req.body[key] !== undefined) {
|
||||
const val = String(req.body[key]);
|
||||
// Don't save masked password
|
||||
if (key === 'smtp_pass' && val === '••••••••') continue;
|
||||
db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)").run(key, val);
|
||||
}
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user