import { backupJobResponseSchema, backupScheduleSettingsDtoSchema, backupStorageSettingsDtoSchema, backupsListResponseSchema, createBackupJobResponseSchema, createBackupRequestSchema, putBackupScheduleSettingsSchema, putBackupStorageSettingsSchema, restoreBackupResponseSchema, syncBackupsResponseSchema, } from "@mmapp/contracts/backups" import type { BackupItemDto, BackupScheduleSettingsDto, PutBackupStorageSettings } from "@mmapp/contracts/backups" import { requestBlob, requestJson } from "@/shared/api/http-client" export type BackupItem = BackupItemDto export type { CreateBackupJobResponse, BackupJobStatusResponse } from "@mmapp/contracts/backups" function backupOptions(options?: RequestInit): RequestInit { return { ...options, signal: AbortSignal.any([...(options?.signal ? [options.signal] : []), AbortSignal.timeout(30_000)]) } } function backupJson(baseUrl: string, path: string, options?: RequestInit) { return requestJson(baseUrl, path, backupOptions(options)) } export async function listBackups(baseUrl: string, options?: RequestInit) { return backupsListResponseSchema.parse(await backupJson(baseUrl, "/api/backups", { ...options, method: "GET" })) } export async function createBackupsAsync(baseUrl: string, payload: { requestId?: string; serverIds: string[]; notes?: string }, options?: RequestInit) { const body = createBackupRequestSchema.parse(payload) const result = createBackupJobResponseSchema.parse(await backupJson(baseUrl, "/api/backups/create", { ...options, method: "POST", body: JSON.stringify(body) })) if (body.requestId && result.jobId !== body.requestId) throw new Error("Подтверждение относится к другому запросу резервного копирования") if (JSON.stringify(result.serverIds) !== JSON.stringify(body.serverIds.map(String))) throw new Error("Не подтверждены выбранные серверы резервного копирования") if (result.total !== body.serverIds.length) throw new Error("Не подтверждено резервное копирование всех выбранных серверов") return result } export async function getBackupJob(baseUrl: string, jobId: string, options?: RequestInit) { const result = backupJobResponseSchema.parse(await backupJson(baseUrl, "/api/backups/jobs/" + encodeURIComponent(jobId), { ...options, method: "GET" })) if (result.id !== jobId) throw new Error("Ответ относится к другой задаче резервного копирования") return result } export async function deleteBackup(baseUrl: string, id: string, options?: RequestInit, recordOnly = false): Promise { const response = await requestBlob(baseUrl, "/api/backups/" + encodeURIComponent(id) + (recordOnly ? "?recordOnly=true" : ""), backupOptions({ ...options, method: "DELETE" })) if (response.status !== 204) throw new Error("Удаление резервной копии не подтверждено: ожидается HTTP 204") if (recordOnly && response.headers.get("X-Backup-Delete-Mode") !== "record-only") throw new Error("Не подтверждено удаление только записи истории. Обновите данные перед повтором.") } export async function restoreBackup(baseUrl: string, id: string, options?: RequestInit, expectedServerId?: number) { const result = restoreBackupResponseSchema.parse(await backupJson(baseUrl, "/api/backups/" + encodeURIComponent(id) + "/restore", { ...options, method: "POST" })) if (result.backupId !== id || (expectedServerId !== undefined && result.serverId !== expectedServerId)) throw new Error("Ответ восстановления относится к другой резервной копии или серверу") return result } export async function getBackupScheduleSettings(baseUrl: string, options?: RequestInit) { return backupScheduleSettingsDtoSchema.parse(await backupJson(baseUrl, "/api/backups/schedule", { ...options, method: "GET" })) } function confirmSettings(result: Record, payload: Record) { for (const [key, value] of Object.entries(payload)) { if (value === undefined) continue if (key === "s3SecretAccessKey") { if (value && result.secretConfigured !== true) throw new Error("Сохранение ключа хранилища не подтверждено") } else if (JSON.stringify(result[key]) !== JSON.stringify(value)) throw new Error("Сохранение настроек не подтверждено: ответ отличается от отправленных данных") } } export async function putBackupScheduleSettings(baseUrl: string, payload: Partial, options?: RequestInit) { const body = putBackupScheduleSettingsSchema.parse(payload) const result = backupScheduleSettingsDtoSchema.parse(await backupJson(baseUrl, "/api/backups/schedule", { ...options, method: "PUT", body: JSON.stringify(body) })) confirmSettings(result, body) return result } export async function getBackupStorageSettings(baseUrl: string, options?: RequestInit) { return backupStorageSettingsDtoSchema.parse(await backupJson(baseUrl, "/api/backups/storage", { ...options, method: "GET" })) } export async function putBackupStorageSettings(baseUrl: string, payload: PutBackupStorageSettings, options?: RequestInit) { const body = putBackupStorageSettingsSchema.parse(payload) const result = backupStorageSettingsDtoSchema.parse(await backupJson(baseUrl, "/api/backups/storage", { ...options, method: "PUT", body: JSON.stringify(body) })) confirmSettings(result, body) return result } export async function testBackupStorage(baseUrl: string, options?: RequestInit) { return backupStorageSettingsDtoSchema.parse(await backupJson(baseUrl, "/api/backups/storage/test", { ...options, method: "POST" })) } export async function syncBackupsFromStorage(baseUrl: string, options?: RequestInit) { return syncBackupsResponseSchema.parse(await backupJson(baseUrl, "/api/backups/storage/sync", { ...options, method: "POST" })) } export async function downloadBackup(baseUrl: string, id: string, options?: RequestInit) { const response = await requestBlob(baseUrl, "/api/backups/" + encodeURIComponent(id) + "/download", backupOptions({ ...options, method: "GET" })) const type = response.headers.get("Content-Type")?.split(";")[0].trim().toLowerCase() if (type !== "text/plain" && type !== "application/octet-stream") throw new Error("Файл резервной копии не подтверждён API") return response.blob() }