fix(backups): проверять ответы и прогресс резервного копирования

This commit is contained in:
Denozordec committed 2026-10-01 14:55:03 +07:00
1 parent 46fe38321a
commit 4ae2954f97
6 files changed
+189 -113

No files matched your search

+42 -5
View File
@@ -63,12 +63,12 @@ export const putBackupStorageSettingsSchema = z.object({
})
export const backupItemDtoSchema = z.object({
id: z.string(),
serverId: z.number().nullable(),
id: z.string().min(1),
serverId: z.number().int().positive().safe().nullable(),
serverName: z.string(),
filename: z.string(),
sizeBytes: z.number(),
createdAt: z.string(),
filename: z.string().min(1),
sizeBytes: z.number().int().nonnegative().safe(),
createdAt: z.string().datetime({ offset: true }),
kind: z.enum(["manual", "auto"]),
notes: z.string().optional(),
storage: backupObjectStorageSchema,
@@ -79,3 +79,40 @@ export const backupItemDtoSchema = z.object({
export type BackupStorageSettingsDto = z.infer<typeof backupStorageSettingsDtoSchema>
export type PutBackupStorageSettings = z.infer<typeof putBackupStorageSettingsSchema>
export type BackupItemDto = z.infer<typeof backupItemDtoSchema>
const backupServerIdSchema = z.union([
z.string().regex(/^[1-9][0-9]*$/).refine(value => Number.isSafeInteger(Number(value))),
z.number().int().positive().safe(),
])
export const createBackupRequestSchema = z.object({
serverIds: z.array(backupServerIdSchema).min(1).refine(ids => new Set(ids.map(String)).size === ids.length, "Дубли серверов"),
notes: z.string().max(500).optional(),
})
export const backupJobStatusSchema = z.enum(["queued", "running", "done", "failed"])
const backupProgressSchema = z.object({
status: backupJobStatusSchema,
total: z.number().int().positive().safe(),
completed: z.number().int().nonnegative().safe(),
})
export const createBackupJobResponseSchema = backupProgressSchema.extend({ jobId: z.string().min(1) }).superRefine((job, ctx) => {
if (job.completed > job.total || (job.status === "done" && job.completed !== job.total)) ctx.addIssue({ code: "custom", message: "Некорректный прогресс задачи" })
})
export const backupJobResponseSchema = backupProgressSchema.extend({
id: z.string().min(1),
requestedAt: z.string().datetime({ offset: true }),
startedAt: z.string().datetime({ offset: true }).optional(),
finishedAt: z.string().datetime({ offset: true }).optional(),
created: z.array(backupItemDtoSchema),
failures: z.array(z.object({ serverId: z.string().min(1), error: z.string().min(1) })),
}).superRefine((job, ctx) => {
if (job.completed > job.total || (job.status === "done" && job.completed !== job.total)) ctx.addIssue({ code: "custom", message: "Некорректный прогресс задачи" })
if (["done", "failed"].includes(job.status) && !job.finishedAt) ctx.addIssue({ code: "custom", message: "Завершение не подтверждено временем" })
const deviceFailures = job.failures.filter(failure => failure.serverId !== "job")
if (job.created.length + deviceFailures.length !== job.completed) ctx.addIssue({ code: "custom", message: "Результаты не соответствуют прогрессу" })
if (new Set(job.created.map(item => item.id)).size !== job.created.length) ctx.addIssue({ code: "custom", message: "Дубли резервных копий" })
})
export const backupsListResponseSchema = z.array(backupItemDtoSchema).refine(items => new Set(items.map(item => item.id)).size === items.length, "Дубли резервных копий")
export const restoreBackupResponseSchema = z.object({ filename: z.string().min(1), serverName: z.string().min(1) })
export const syncBackupsResponseSchema = z.object({ imported: z.number().int().nonnegative().safe(), skipped: z.number().int().nonnegative().safe() })
export type CreateBackupJobResponse = z.infer<typeof createBackupJobResponseSchema>
export type BackupJobStatusResponse = z.infer<typeof backupJobResponseSchema>