feat: add settings table and backup notification functionality
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -47,4 +47,6 @@ next-env.d.ts
|
||||
/kosz
|
||||
|
||||
# uploads
|
||||
/public/uploads
|
||||
/public/uploads
|
||||
|
||||
/backups
|
||||
@@ -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-'))
|
||||
|
||||
25
migrate-add-settings-table.mjs
Normal file
25
migrate-add-settings-table.mjs
Normal file
@@ -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);
|
||||
}
|
||||
183
src/app/admin/settings/page.js
Normal file
183
src/app/admin/settings/page.js
Normal file
@@ -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 (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-lg">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!session || session.user.role !== "admin") {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-gray-800 mb-4">
|
||||
Access Denied
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-6">
|
||||
You need admin privileges to access this page.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
>
|
||||
Go Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const backupUserSetting = settings.find(s => s.key === "backup_notification_user_id");
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
Admin Settings
|
||||
</h1>
|
||||
<Link
|
||||
href="/admin"
|
||||
className="text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
← Back to Admin
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
{/* Backup Notifications Setting */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
Backup Notifications
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
Select which user should receive notifications when daily database backups are completed.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Notification Recipient
|
||||
</label>
|
||||
<select
|
||||
value={backupUserSetting?.value || ""}
|
||||
onChange={(e) => handleBackupUserChange(e.target.value)}
|
||||
disabled={saving}
|
||||
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
|
||||
>
|
||||
<option value="">No notifications</option>
|
||||
{users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.name} ({user.username}) - {user.role}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{saving && (
|
||||
<p className="text-sm text-blue-600">Saving...</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Future settings can be added here */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
System Information
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
Daily database backups run automatically at 2 AM and keep the last 30 backups.
|
||||
Backups are stored in the <code className="bg-gray-100 px-1 rounded">./backups/</code> directory.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
52
src/app/api/admin/settings/route.js
Normal file
52
src/app/api/admin/settings/route.js
Normal file
@@ -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);
|
||||
@@ -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');
|
||||
`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user