feat: database backups & analytics site management
- Added manual database backup functionality with pg_dump - Added cron-based automated backups to Google Drive and Dropbox - Added ability to manually add, edit, and delete sites in Analytics - Added 1-day timeframe filter in Analytics page - Updated Dockerfile to include postgresql-client
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import pool from '@/lib/appDb'
|
||||
import { generateId } from '@/lib/config'
|
||||
import { reloadBackupCrons } from '@/lib/cronWorker'
|
||||
|
||||
// GET configs and logs for a dbId
|
||||
export async function GET(req: Request) {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const dbId = searchParams.get('dbId')
|
||||
if (!dbId) return NextResponse.json({ error: 'Missing dbId' }, { status: 400 })
|
||||
|
||||
try {
|
||||
const configRes = await pool.query(`SELECT * FROM backup_configs WHERE db_id = $1 LIMIT 1`, [dbId])
|
||||
const logsRes = await pool.query(`SELECT * FROM backup_logs WHERE db_id = $1 ORDER BY ts DESC LIMIT 50`, [dbId])
|
||||
|
||||
return NextResponse.json({
|
||||
config: configRes.rows[0] || null,
|
||||
logs: logsRes.rows.map(r => ({ ...r, file_size: Number(r.file_size), ts: Number(r.ts) }))
|
||||
})
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e.message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update a backup config
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { db_id, schedule, cloud_type, credentials } = body
|
||||
|
||||
if (!db_id || !schedule || !cloud_type || !credentials) {
|
||||
return NextResponse.json({ error: 'Missing fields' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Check if exists
|
||||
const existing = await pool.query(`SELECT id FROM backup_configs WHERE db_id = $1`, [db_id])
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
await pool.query(
|
||||
`UPDATE backup_configs SET schedule=$1, cloud_type=$2, credentials=$3 WHERE db_id=$4`,
|
||||
[schedule, cloud_type, credentials, db_id]
|
||||
)
|
||||
} else {
|
||||
await pool.query(
|
||||
`INSERT INTO backup_configs (id, db_id, schedule, cloud_type, credentials) VALUES ($1, $2, $3, $4, $5)`,
|
||||
[generateId(), db_id, schedule, cloud_type, credentials]
|
||||
)
|
||||
}
|
||||
|
||||
// Reload crons in memory
|
||||
await reloadBackupCrons()
|
||||
|
||||
return NextResponse.json({ ok: true })
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e.message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// Delete backup config
|
||||
export async function DELETE(req: Request) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { db_id } = body
|
||||
if (!db_id) return NextResponse.json({ error: 'Missing dbId' }, { status: 400 })
|
||||
|
||||
await pool.query(`DELETE FROM backup_configs WHERE db_id = $1`, [db_id])
|
||||
await reloadBackupCrons()
|
||||
|
||||
return NextResponse.json({ ok: true })
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e.message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user