feat: Schritte 14–15 — Google Calendar OAuth + Apple CalDAV Sync + Settings-Seite
- server/services/google-calendar.js: OAuth 2.0, bidirektionaler Sync via Google Calendar API v3, inkrementeller syncToken, 410-Fallback auf Vollsync - server/services/apple-calendar.js: CalDAV via tsdav (dynamic ESM import), minimaler ICS-Parser + ICS-Builder, bidirektionaler Sync - server/routes/calendar.js: 7 neue Sync-Routen (google/auth, google/callback, google/sync, google/status, google/disconnect, apple/status, apple/sync) - server/db.js: Migration 2 — sync_config Tabelle + idx_calendar_external_id - server/db-schema-test.js: MIGRATIONS_SQL[2] für Tests synchronisiert - server/auth.js: PATCH /me/password Endpoint - server/index.js: Auto-Sync-Scheduler (setInterval, SYNC_INTERVAL_MINUTES) - public/pages/settings.js: vollständige Settings-Seite (Konto, Passwort, Kalender-Sync-Status + Aktionen, Familienmitglieder-Verwaltung) - public/styles/settings.css: neue Stylesheet-Datei - public/index.html + public/sw.js: settings.css eingebunden und gecacht - .env.example: SYNC_INTERVAL_MINUTES ergänzt - README.md: vollständige Setup-Anleitung, Google/Apple-Sync-Dokumentation, modernes GitHub-Layout mit Badges und aufklappbaren Abschnitten Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -239,6 +239,39 @@ router.post('/users', requireAuth, requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /api/v1/auth/me/password
|
||||
* Ändert das eigene Passwort.
|
||||
* Body: { current_password: string, new_password: string }
|
||||
* Response: { ok: true }
|
||||
*/
|
||||
router.patch('/me/password', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { current_password, new_password } = req.body;
|
||||
|
||||
if (!current_password || !new_password) {
|
||||
return res.status(400).json({ error: 'Aktuelles und neues Passwort erforderlich.', code: 400 });
|
||||
}
|
||||
if (new_password.length < 8) {
|
||||
return res.status(400).json({ error: 'Neues Passwort muss mindestens 8 Zeichen haben.', code: 400 });
|
||||
}
|
||||
|
||||
const user = db.get().prepare('SELECT password_hash FROM users WHERE id = ?').get(req.session.userId);
|
||||
if (!user) return res.status(404).json({ error: 'Benutzer nicht gefunden.', code: 404 });
|
||||
|
||||
const valid = await bcrypt.compare(current_password, user.password_hash);
|
||||
if (!valid) return res.status(401).json({ error: 'Aktuelles Passwort falsch.', code: 401 });
|
||||
|
||||
const hash = await bcrypt.hash(new_password, 12);
|
||||
db.get().prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(hash, req.session.userId);
|
||||
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
console.error('[Auth] Passwort-Ändern-Fehler:', err);
|
||||
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/v1/auth/users/:id
|
||||
* Admin only. Löscht ein Familienmitglied.
|
||||
|
||||
Reference in New Issue
Block a user