fix(backups): сохранить идентичность фоновой задачи

This commit is contained in:
Denozordec committed 2026-10-01 15:44:02 +07:00
1 parent 30bd479a6f
commit 4aee2a34f1
13 files changed
+157 -12

No files matched your search

+9 -2
View File
@@ -85,8 +85,9 @@ const backupServerIdSchema = z.union([
z.number().int().positive().safe(),
])
export const createBackupRequestSchema = z.object({
requestId: z.string().uuid().optional(),
serverIds: z.array(backupServerIdSchema).min(1).refine(ids => new Set(ids.map(String)).size === ids.length, "Дубли серверов"),
notes: z.string().max(500).optional(),
notes: z.string().trim().max(500).optional(),
})
export const backupJobStatusSchema = z.enum(["queued", "running", "done", "failed"])
const backupProgressSchema = z.object({
@@ -94,11 +95,14 @@ const backupProgressSchema = z.object({
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) => {
export const createBackupJobResponseSchema = backupProgressSchema.extend({ jobId: z.string().min(1), serverIds: z.array(z.string().min(1)).min(1) }).superRefine((job, ctx) => {
if (job.serverIds.length !== job.total || new Set(job.serverIds).size !== job.total) ctx.addIssue({ code: "custom", message: "Цели не соответствуют числу устройств" })
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),
serverIds: z.array(z.string().regex(/^[1-9][0-9]*$/)).min(1),
notes: z.string().optional(),
requestedAt: z.string().datetime({ offset: true }),
startedAt: z.string().datetime({ offset: true }).optional(),
finishedAt: z.string().datetime({ offset: true }).optional(),
@@ -107,6 +111,7 @@ export const backupJobResponseSchema = backupProgressSchema.extend({
}).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: "Завершение не подтверждено временем" })
if (job.created.some(item => item.serverId === null || !job.serverIds.includes(String(item.serverId))) || job.failures.some(failure => failure.serverId !== "job" && !job.serverIds.includes(failure.serverId))) 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: "Дубли резервных копий" })
@@ -116,3 +121,5 @@ export const restoreBackupResponseSchema = z.object({ filename: 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>
export type CreateBackupRequest = z.infer<typeof createBackupRequestSchema>