feat: notifications, audit logging, and admin improvements

- Add centralized notification service with webhook (Discord/Slack) and
  email (SMTP) support, triggered for trip invites, booking changes,
  collab messages, and trip reminders
- Webhook sends one message per event (group channel); email sends
  individually per trip member, excluding the actor
- Discord invite notifications now include the invited user's name
- Add LOG_LEVEL env var (info/debug) controlling console and file output
- INFO logs show user email, action, and IP for audit events; errors
  for HTTP requests
- DEBUG logs show every request with full body/query (passwords redacted),
  audit details, notification params, and webhook payloads
- Add persistent trek.log file logging with 10MB rotation (5 files)
  in /app/data/logs/
- Color-coded log levels in Docker console output
- Timestamps without timezone name (user sets TZ via Docker)
- Add Test Webhook and Save buttons to admin notification settings
- Move notification event toggles to admin panel
- Add daily trip reminder scheduler (9 AM, timezone-aware)
- Wire up booking create/update/delete and collab message notifications
- Add i18n keys for notification UI across all 13 languages

Made-with: Cursor
This commit is contained in:
Andrei Brebene
2026-03-31 15:01:33 +03:00
parent f7160e6dec
commit 9b2f083e4b
35 changed files with 1004 additions and 249 deletions
+3 -1
View File
@@ -428,9 +428,11 @@ function runMigrations(db: Database.Database): void {
} catch {}
},
() => {
// GPX full route geometry stored as JSON array of [lat,lng] pairs
try { db.exec('ALTER TABLE places ADD COLUMN route_geometry TEXT'); } catch {}
},
() => {
try { db.exec('ALTER TABLE users ADD COLUMN must_change_password INTEGER DEFAULT 0'); } catch {}
},
];
if (currentVersion < migrations.length) {
+1
View File
@@ -18,6 +18,7 @@ function createTables(db: Database.Database): void {
mfa_enabled INTEGER DEFAULT 0,
mfa_secret TEXT,
mfa_backup_codes TEXT,
must_change_password INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
+28
View File
@@ -1,4 +1,31 @@
import Database from 'better-sqlite3';
import crypto from 'crypto';
function seedAdminAccount(db: Database.Database): void {
try {
const userCount = (db.prepare('SELECT COUNT(*) as count FROM users').get() as { count: number }).count;
if (userCount > 0) return;
const bcrypt = require('bcryptjs');
const password = crypto.randomBytes(12).toString('base64url');
const hash = bcrypt.hashSync(password, 12);
const email = 'admin@trek.local';
const username = 'admin';
db.prepare('INSERT INTO users (username, email, password_hash, role, must_change_password) VALUES (?, ?, ?, ?, 1)').run(username, email, hash, 'admin');
console.log('');
console.log('╔══════════════════════════════════════════════╗');
console.log('║ TREK — First Run: Admin Account Created ║');
console.log('╠══════════════════════════════════════════════╣');
console.log(`║ Email: ${email.padEnd(33)}`);
console.log(`║ Password: ${password.padEnd(33)}`);
console.log('╚══════════════════════════════════════════════╝');
console.log('');
} catch (err: unknown) {
console.error('[ERROR] Error seeding admin account:', err instanceof Error ? err.message : err);
}
}
function seedCategories(db: Database.Database): void {
try {
@@ -45,6 +72,7 @@ function seedAddons(db: Database.Database): void {
}
function runSeeds(db: Database.Database): void {
seedAdminAccount(db);
seedCategories(db);
seedAddons(db);
}