feat: add cron job management functionality with status retrieval and action handling
This commit is contained in:
191
src/app/api/admin/cron/route.js
Normal file
191
src/app/api/admin/cron/route.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { withAdminAuth } from "@/lib/middleware/auth";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// Check if we're running in a Linux/Docker environment
|
||||
const isLinux = process.platform === "linux";
|
||||
|
||||
async function getCronStatus() {
|
||||
if (!isLinux) {
|
||||
return {
|
||||
available: false,
|
||||
running: false,
|
||||
jobs: [],
|
||||
message: "Cron is only available in Linux/Docker environment",
|
||||
lastBackup: getLastBackupInfo(),
|
||||
lastReminder: getLastReminderInfo()
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if cron daemon is running
|
||||
let cronRunning = false;
|
||||
try {
|
||||
await execAsync("pgrep -x cron || pgrep -x crond");
|
||||
cronRunning = true;
|
||||
} catch {
|
||||
cronRunning = false;
|
||||
}
|
||||
|
||||
// Get current crontab
|
||||
let jobs = [];
|
||||
try {
|
||||
const { stdout } = await execAsync("crontab -l 2>/dev/null");
|
||||
jobs = stdout.trim().split("\n").filter(line => line && !line.startsWith("#"));
|
||||
} catch {
|
||||
jobs = [];
|
||||
}
|
||||
|
||||
return {
|
||||
available: true,
|
||||
running: cronRunning,
|
||||
jobs: jobs,
|
||||
jobCount: jobs.length,
|
||||
lastBackup: getLastBackupInfo(),
|
||||
lastReminder: getLastReminderInfo()
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
available: false,
|
||||
running: false,
|
||||
jobs: [],
|
||||
error: error.message,
|
||||
lastBackup: getLastBackupInfo(),
|
||||
lastReminder: getLastReminderInfo()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getLastBackupInfo() {
|
||||
try {
|
||||
const backupDir = path.join(process.cwd(), "backups");
|
||||
if (!fs.existsSync(backupDir)) {
|
||||
return { exists: false, message: "No backups directory" };
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(backupDir)
|
||||
.filter(f => f.startsWith("backup-") && f.endsWith(".sqlite"))
|
||||
.map(f => ({
|
||||
name: f,
|
||||
path: path.join(backupDir, f),
|
||||
mtime: fs.statSync(path.join(backupDir, f)).mtime
|
||||
}))
|
||||
.sort((a, b) => b.mtime - a.mtime);
|
||||
|
||||
if (files.length === 0) {
|
||||
return { exists: false, message: "No backups found" };
|
||||
}
|
||||
|
||||
const latest = files[0];
|
||||
return {
|
||||
exists: true,
|
||||
filename: latest.name,
|
||||
date: latest.mtime.toISOString(),
|
||||
count: files.length
|
||||
};
|
||||
} catch (error) {
|
||||
return { exists: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
function getLastReminderInfo() {
|
||||
try {
|
||||
const logPath = path.join(process.cwd(), "data", "reminders.log");
|
||||
if (!fs.existsSync(logPath)) {
|
||||
return { exists: false, message: "No reminders log" };
|
||||
}
|
||||
|
||||
const stats = fs.statSync(logPath);
|
||||
return {
|
||||
exists: true,
|
||||
lastModified: stats.mtime.toISOString()
|
||||
};
|
||||
} catch (error) {
|
||||
return { exists: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function getHandler() {
|
||||
const status = await getCronStatus();
|
||||
return NextResponse.json(status);
|
||||
}
|
||||
|
||||
async function postHandler(request) {
|
||||
const { action } = await request.json();
|
||||
|
||||
if (!isLinux) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: "Cron operations are only available in Linux/Docker environment"
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
if (action === "restart") {
|
||||
// Run the setup-cron.sh script
|
||||
const scriptPath = path.join(process.cwd(), "setup-cron.sh");
|
||||
|
||||
if (!fs.existsSync(scriptPath)) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: "setup-cron.sh script not found"
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
// Make sure script is executable
|
||||
await execAsync(`chmod +x ${scriptPath}`);
|
||||
|
||||
// Run the script
|
||||
const { stdout, stderr } = await execAsync(`bash ${scriptPath}`);
|
||||
|
||||
// Get updated status
|
||||
const status = await getCronStatus();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Cron jobs restarted successfully",
|
||||
output: stdout,
|
||||
status
|
||||
});
|
||||
} else if (action === "run-backup") {
|
||||
// Manually trigger backup
|
||||
const backupScript = path.join(process.cwd(), "backup-db.mjs");
|
||||
const { stdout } = await execAsync(`cd ${process.cwd()} && node ${backupScript}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Backup completed",
|
||||
output: stdout
|
||||
});
|
||||
} else if (action === "run-reminders") {
|
||||
// Manually trigger reminders
|
||||
const reminderScript = path.join(process.cwd(), "send-due-date-reminders.mjs");
|
||||
const { stdout } = await execAsync(`cd ${process.cwd()} && node ${reminderScript}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Reminders sent",
|
||||
output: stdout
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: "Unknown action"
|
||||
}, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
stderr: error.stderr
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export const GET = withAdminAuth(getHandler);
|
||||
export const POST = withAdminAuth(postHandler);
|
||||
Reference in New Issue
Block a user