From eb41814c24ddf351859d1efa525fdce90188b1fc Mon Sep 17 00:00:00 2001 From: RKWojs Date: Tue, 2 Dec 2025 11:30:13 +0100 Subject: [PATCH] feat: add settings table and backup notification functionality --- .gitignore | 4 +- backup-db.mjs | 21 ++++ migrate-add-settings-table.mjs | 25 ++++ src/app/admin/settings/page.js | 183 ++++++++++++++++++++++++++++ src/app/api/admin/settings/route.js | 52 ++++++++ src/lib/init-db.js | 14 +++ 6 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 migrate-add-settings-table.mjs create mode 100644 src/app/admin/settings/page.js create mode 100644 src/app/api/admin/settings/route.js diff --git a/.gitignore b/.gitignore index d8b7da2..3d1534d 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,6 @@ next-env.d.ts /kosz # uploads -/public/uploads \ No newline at end of file +/public/uploads + +/backups \ No newline at end of file diff --git a/backup-db.mjs b/backup-db.mjs index e5082f4..9a599f8 100644 --- a/backup-db.mjs +++ b/backup-db.mjs @@ -19,6 +19,27 @@ fs.copyFileSync(dbPath, backupPath); console.log(`✅ Backup created: ${backupPath}`); +// Send notification if configured +try { + const { createNotification, NOTIFICATION_TYPES } = await import("./src/lib/notifications.js"); + const db = (await import("./src/lib/db.js")).default; + + const setting = db.prepare("SELECT value FROM settings WHERE key = 'backup_notification_user_id'").get(); + if (setting && setting.value) { + const userId = setting.value; + await createNotification({ + userId, + type: NOTIFICATION_TYPES.SYSTEM_ANNOUNCEMENT, + title: "Database Backup Completed", + message: `Daily database backup completed successfully. Backup file: ${backupPath}`, + priority: "normal" + }); + console.log(`📢 Notification sent to user ${userId}`); + } +} catch (error) { + console.error("Failed to send backup notification:", error); +} + // Cleanup: keep only last 30 backups const files = fs.readdirSync(backupDir) .filter(f => f.startsWith('backup-')) diff --git a/migrate-add-settings-table.mjs b/migrate-add-settings-table.mjs new file mode 100644 index 0000000..e602469 --- /dev/null +++ b/migrate-add-settings-table.mjs @@ -0,0 +1,25 @@ +import db from "./src/lib/db.js"; + +console.log("Adding settings table..."); + +try { + db.exec(` + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + description TEXT, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP, + updated_by TEXT, + FOREIGN KEY (updated_by) REFERENCES users(id) + ); + `); + + db.exec(` + INSERT OR IGNORE INTO settings (key, value, description) VALUES + ('backup_notification_user_id', '', 'User ID to receive backup completion notifications'); + `); + + console.log("✅ Settings table created successfully"); +} catch (error) { + console.error("Error creating settings table:", error); +} \ No newline at end of file diff --git a/src/app/admin/settings/page.js b/src/app/admin/settings/page.js new file mode 100644 index 0000000..65a6c14 --- /dev/null +++ b/src/app/admin/settings/page.js @@ -0,0 +1,183 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; + +export default function AdminSettingsPage() { + const { data: session, status } = useSession(); + const router = useRouter(); + const [settings, setSettings] = useState([]); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [users, setUsers] = useState([]); + + // Redirect if not admin + useEffect(() => { + if (status === "loading") return; + if (!session || session.user.role !== "admin") { + router.push("/"); + return; + } + fetchSettings(); + fetchUsers(); + }, [session, status, router]); + + const fetchSettings = async () => { + try { + const response = await fetch("/api/admin/settings"); + if (response.ok) { + const data = await response.json(); + setSettings(data); + } + } catch (error) { + console.error("Error fetching settings:", error); + } finally { + setLoading(false); + } + }; + + const fetchUsers = async () => { + try { + const response = await fetch("/api/admin/users"); + if (response.ok) { + const data = await response.json(); + setUsers(data); + } + } catch (error) { + console.error("Error fetching users:", error); + } + }; + + const updateSetting = async (key, value) => { + setSaving(true); + try { + const response = await fetch("/api/admin/settings", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ key, value }), + }); + + if (response.ok) { + // Update local state + setSettings(prev => + prev.map(setting => + setting.key === key ? { ...setting, value } : setting + ) + ); + alert("Setting updated successfully!"); + } else { + alert("Failed to update setting"); + } + } catch (error) { + console.error("Error updating setting:", error); + alert("Error updating setting"); + } finally { + setSaving(false); + } + }; + + const handleBackupUserChange = (userId) => { + updateSetting("backup_notification_user_id", userId); + }; + + if (status === "loading" || loading) { + return ( +
+
Loading...
+
+ ); + } + + if (!session || session.user.role !== "admin") { + return ( +
+
+

+ Access Denied +

+

+ You need admin privileges to access this page. +

+ + Go Home + +
+
+ ); + } + + const backupUserSetting = settings.find(s => s.key === "backup_notification_user_id"); + + return ( +
+
+
+
+
+

+ Admin Settings +

+ + ← Back to Admin + +
+
+ +
+ {/* Backup Notifications Setting */} +
+

+ Backup Notifications +

+

+ Select which user should receive notifications when daily database backups are completed. +

+
+ + + {saving && ( +

Saving...

+ )} +
+
+ + {/* Future settings can be added here */} +
+

+ System Information +

+

+ Daily database backups run automatically at 2 AM and keep the last 30 backups. + Backups are stored in the ./backups/ directory. +

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/api/admin/settings/route.js b/src/app/api/admin/settings/route.js new file mode 100644 index 0000000..7153207 --- /dev/null +++ b/src/app/api/admin/settings/route.js @@ -0,0 +1,52 @@ +import { NextResponse } from "next/server"; +import { withAdminAuth } from "@/lib/middleware/auth"; +import db from "@/lib/db"; + +// GET: Get all settings +async function getSettingsHandler() { + try { + const settings = db.prepare("SELECT * FROM settings ORDER BY key").all(); + return NextResponse.json(settings); + } catch (error) { + console.error("Error fetching settings:", error); + return NextResponse.json( + { error: "Failed to fetch settings" }, + { status: 500 } + ); + } +} + +// PUT: Update a setting +async function updateSettingHandler(request) { + try { + const { key, value } = await request.json(); + + if (!key || value === undefined) { + return NextResponse.json( + { error: "Key and value are required" }, + { status: 400 } + ); + } + + const updatedBy = request.user.id; + + const stmt = db.prepare(` + INSERT OR REPLACE INTO settings (key, value, updated_at, updated_by) + VALUES (?, ?, CURRENT_TIMESTAMP, ?) + `); + + stmt.run(key, value, updatedBy); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error updating setting:", error); + return NextResponse.json( + { error: "Failed to update setting" }, + { status: 500 } + ); + } +} + +// Protected routes - require admin authentication +export const GET = withAdminAuth(getSettingsHandler); +export const PUT = withAdminAuth(updateSettingHandler); \ No newline at end of file diff --git a/src/lib/init-db.js b/src/lib/init-db.js index 8d6a539..2d93177 100644 --- a/src/lib/init-db.js +++ b/src/lib/init-db.js @@ -500,5 +500,19 @@ export default function initializeDatabase() { CREATE INDEX IF NOT EXISTS idx_notifications_user_read ON notifications(user_id, is_read); CREATE INDEX IF NOT EXISTS idx_notifications_created ON notifications(created_at); CREATE INDEX IF NOT EXISTS idx_notifications_type ON notifications(type); + + -- Settings table for application configuration + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + description TEXT, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP, + updated_by TEXT, + FOREIGN KEY (updated_by) REFERENCES users(id) + ); + + -- Insert default settings + INSERT OR IGNORE INTO settings (key, value, description) VALUES + ('backup_notification_user_id', '', 'User ID to receive backup completion notifications'); `); }