data export and backup utilities
This commit is contained in:
49
lib/backup-utils.ts
Normal file
49
lib/backup-utils.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { exec } from 'child_process'
|
||||||
|
import path from 'path'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
|
export async function createDatabaseBackup(): Promise<string> {
|
||||||
|
const dbPath = path.join(process.cwd(), 'database', 'antihoax.db')
|
||||||
|
const backupPath = path.join(process.cwd(), 'backups', `backup_${Date.now()}.db`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure backup directory exists
|
||||||
|
await execAsync('mkdir -p backups')
|
||||||
|
|
||||||
|
// Create SQLite backup using .backup command
|
||||||
|
await execAsync(`sqlite3 "${dbPath}" ".backup '${backupPath}'"`)
|
||||||
|
|
||||||
|
return backupPath
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Backup failed:', error)
|
||||||
|
throw new Error('Database backup failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function restoreDatabase(backupPath: string): Promise<void> {
|
||||||
|
const dbPath = path.join(process.cwd(), 'database', 'antihoax.db')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create backup of current DB first
|
||||||
|
await createDatabaseBackup()
|
||||||
|
|
||||||
|
// Copy backup to main location
|
||||||
|
await execAsync(`cp "${backupPath}" "${dbPath}"`)
|
||||||
|
|
||||||
|
console.log('Database restored successfully')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Restore failed:', error)
|
||||||
|
throw new Error('Database restore failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listBackups(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const { stdout } = await execAsync('ls -la backups/*.db 2>/dev/null || true')
|
||||||
|
return stdout.trim() ? stdout.trim().split('\n') : []
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
84
pages/api/admin/export.ts
Normal file
84
pages/api/admin/export.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
import sqlite3 from "sqlite3"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (req.method !== "GET") return res.status(405).json({ error: "Method not allowed" })
|
||||||
|
|
||||||
|
const { format = 'json', type = 'sources' } = req.query
|
||||||
|
|
||||||
|
const dbPath = path.join(process.cwd(), "database", "antihoax.db")
|
||||||
|
const db = new sqlite3.Database(dbPath)
|
||||||
|
|
||||||
|
try {
|
||||||
|
let query = ""
|
||||||
|
let filename = ""
|
||||||
|
|
||||||
|
if (type === 'sources') {
|
||||||
|
query = `
|
||||||
|
SELECT s.domain, s.risk_level, s.status, s.created_at,
|
||||||
|
GROUP_CONCAT(c.name) as categories
|
||||||
|
FROM sources s
|
||||||
|
LEFT JOIN source_categories sc ON s.id = sc.source_id
|
||||||
|
LEFT JOIN categories c ON sc.category_id = c.id
|
||||||
|
WHERE s.status = 'verified'
|
||||||
|
GROUP BY s.id
|
||||||
|
ORDER BY s.risk_level DESC
|
||||||
|
`
|
||||||
|
filename = `sources_export_${Date.now()}.${format}`
|
||||||
|
} else if (type === 'reports') {
|
||||||
|
query = `
|
||||||
|
SELECT source_url, status, categories, description, created_at
|
||||||
|
FROM reports
|
||||||
|
WHERE status != 'spam'
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
filename = `reports_export_${Date.now()}.${format}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await new Promise<any[]>((resolve, reject) => {
|
||||||
|
db.all(query, (err, rows) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(rows)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (format === 'csv') {
|
||||||
|
// Convert to CSV
|
||||||
|
if (data.length === 0) {
|
||||||
|
return res.status(200).send('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = Object.keys(data[0]).join(',')
|
||||||
|
const csvRows = data.map(row =>
|
||||||
|
Object.values(row).map(value =>
|
||||||
|
typeof value === 'string' && value.includes(',')
|
||||||
|
? `"${value.replace(/"/g, '""')}"`
|
||||||
|
: value
|
||||||
|
).join(',')
|
||||||
|
)
|
||||||
|
|
||||||
|
const csvContent = [headers, ...csvRows].join('\n')
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'text/csv')
|
||||||
|
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`)
|
||||||
|
res.send(csvContent)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// JSON format
|
||||||
|
res.setHeader('Content-Type', 'application/json')
|
||||||
|
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`)
|
||||||
|
res.json({
|
||||||
|
exported_at: new Date().toISOString(),
|
||||||
|
count: data.length,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Export error:', error)
|
||||||
|
res.status(500).json({ error: "Export failed" })
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user