86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
import type { FastifyPluginAsync } from "fastify"
|
|
import { appendEvent } from "../modules/events/service/events-service.js"
|
|
import {
|
|
exportSystemDatabaseBackup,
|
|
restoreSystemDatabaseBackup,
|
|
} from "../services/system-database-backup.js"
|
|
|
|
interface Dependencies {
|
|
exportBackup: typeof exportSystemDatabaseBackup
|
|
restoreBackup: typeof restoreSystemDatabaseBackup
|
|
event: typeof appendEvent
|
|
}
|
|
export function createSystemDatabaseRoutes(deps: Dependencies): FastifyPluginAsync { return async (app) => {
|
|
app.addContentTypeParser(
|
|
"application/octet-stream",
|
|
{ parseAs: "buffer", bodyLimit: 512 * 1024 * 1024 },
|
|
(_req, body, done) => {
|
|
done(null, body)
|
|
},
|
|
)
|
|
|
|
app.get("/system/database/backup", async (_req, reply) => {
|
|
try {
|
|
const { filename, buffer, ...warnings } = await deps.exportBackup()
|
|
let eventError: string | undefined
|
|
try {
|
|
await deps.event({
|
|
level: "info",
|
|
eventType: "system.database.backup",
|
|
sourceModule: "system",
|
|
title: "Создан бэкап базы приложения",
|
|
message: filename,
|
|
entityType: "system_database",
|
|
entityId: filename,
|
|
payload: {
|
|
filename,
|
|
sizeBytes: buffer.length,
|
|
},
|
|
})
|
|
} catch { eventError = "Не удалось записать событие резервного копирования" }
|
|
const outcome = { ...warnings, ...(eventError ? { eventError } : {}) }
|
|
reply.header("X-System-Database-Outcome", encodeURIComponent(JSON.stringify(outcome)))
|
|
reply.header("Content-Type", "application/octet-stream")
|
|
reply.header("Content-Disposition", `attachment; filename="${filename}"`)
|
|
return reply.send(buffer)
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err)
|
|
return reply.status(message.includes("уже выполняется") ? 409 : 500).send({ error: message })
|
|
}
|
|
})
|
|
|
|
app.post("/system/database/restore", async (req, reply) => {
|
|
const body = req.body
|
|
if (!Buffer.isBuffer(body) || body.length === 0) {
|
|
return reply.status(400).send({ error: "Ожидается тело запроса с файлом pg_dump (custom, PGDMP)" })
|
|
}
|
|
try {
|
|
const outcome = await deps.restoreBackup(body)
|
|
let eventError: string | undefined
|
|
try {
|
|
await deps.event({
|
|
level: "warning",
|
|
eventType: "system.database.restore",
|
|
sourceModule: "system",
|
|
title: "Восстановлена база приложения",
|
|
message: "Данные PostgreSQL заменены из загруженного дампа",
|
|
entityType: "system_database",
|
|
entityId: "restore",
|
|
payload: {
|
|
sizeBytes: body.length,
|
|
},
|
|
})
|
|
} catch { eventError = "Не удалось записать событие восстановления" }
|
|
return reply.send({ ok: true, ...outcome, ...(eventError ? { eventError } : {}) })
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err)
|
|
const status = message.includes("уже выполняется") ? 409 : 400
|
|
return reply.status(status).send({ error: message })
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
export default createSystemDatabaseRoutes({ exportBackup: exportSystemDatabaseBackup, restoreBackup: restoreSystemDatabaseBackup, event: appendEvent })
|