126 lines
6.7 KiB
TypeScript
126 lines
6.7 KiB
TypeScript
import { z } from "zod"
|
|
|
|
export const backupFrequencySchema = z.enum(["daily", "weekly", "monthly"])
|
|
export const backupFormatSchema = z.enum(["rsc", "backup"])
|
|
export const backupStorageProviderSchema = z.enum(["local", "s3"])
|
|
export const backupObjectStorageSchema = z.enum(["local", "s3", "both"])
|
|
|
|
export const backupScheduleSettingsDtoSchema = z.object({
|
|
enabled: z.boolean(),
|
|
frequency: backupFrequencySchema,
|
|
hour: z.number().int().min(0).max(23),
|
|
minute: z.number().int().min(0).max(59),
|
|
weekDay: z.number().int().min(0).max(6),
|
|
monthDay: z.number().int().min(1).max(28),
|
|
keepCount: z.number().int().min(1).max(365),
|
|
format: backupFormatSchema,
|
|
serverIds: z.array(z.string()),
|
|
lastRunAt: z.string().nullable().optional(),
|
|
lastDurationMs: z.number().int().nullable().optional(),
|
|
lastError: z.string().nullable().optional(),
|
|
updatedAt: z.string().optional(),
|
|
})
|
|
|
|
export const putBackupScheduleSettingsSchema = z.object({
|
|
enabled: z.boolean().optional(),
|
|
frequency: backupFrequencySchema.optional(),
|
|
hour: z.number().int().min(0).max(23).optional(),
|
|
minute: z.number().int().min(0).max(59).optional(),
|
|
weekDay: z.number().int().min(0).max(6).optional(),
|
|
monthDay: z.number().int().min(1).max(28).optional(),
|
|
keepCount: z.number().int().min(1).max(365).optional(),
|
|
format: backupFormatSchema.optional(),
|
|
serverIds: z.array(z.string()).optional(),
|
|
})
|
|
|
|
export type BackupScheduleSettingsDto = z.infer<typeof backupScheduleSettingsDtoSchema>
|
|
|
|
export const backupStorageSettingsDtoSchema = z.object({
|
|
provider: backupStorageProviderSchema,
|
|
s3Endpoint: z.string(),
|
|
s3Region: z.string(),
|
|
s3Bucket: z.string(),
|
|
s3Prefix: z.string(),
|
|
s3AccessKeyId: z.string(),
|
|
secretConfigured: z.boolean(),
|
|
s3ForcePathStyle: z.boolean(),
|
|
keepLocalCopy: z.boolean(),
|
|
lastTestAt: z.string().nullable().optional(),
|
|
lastTestError: z.string().nullable().optional(),
|
|
updatedAt: z.string().optional(),
|
|
})
|
|
|
|
export const putBackupStorageSettingsSchema = z.object({
|
|
provider: backupStorageProviderSchema.optional(),
|
|
s3Endpoint: z.string().optional(),
|
|
s3Region: z.string().optional(),
|
|
s3Bucket: z.string().optional(),
|
|
s3Prefix: z.string().optional(),
|
|
s3AccessKeyId: z.string().optional(),
|
|
s3SecretAccessKey: z.string().optional(),
|
|
s3ForcePathStyle: z.boolean().optional(),
|
|
keepLocalCopy: z.boolean().optional(),
|
|
})
|
|
|
|
export const backupItemDtoSchema = z.object({
|
|
id: z.string().min(1),
|
|
serverId: z.number().int().positive().safe().nullable(),
|
|
serverName: 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,
|
|
s3Key: z.string().nullable().optional(),
|
|
uploadError: z.string().nullable().optional(),
|
|
})
|
|
|
|
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({
|
|
requestId: z.string().uuid().optional(),
|
|
serverIds: z.array(backupServerIdSchema).min(1).refine(ids => new Set(ids.map(String)).size === ids.length, "Дубли серверов"),
|
|
notes: z.string().trim().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), 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(),
|
|
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: "Завершение не подтверждено временем" })
|
|
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: "Дубли резервных копий" })
|
|
})
|
|
export const backupsListResponseSchema = z.array(backupItemDtoSchema).refine(items => new Set(items.map(item => item.id)).size === items.length, "Дубли резервных копий")
|
|
export const restoreBackupResponseSchema = z.object({ backupId: z.string().min(1), serverId: z.number().int().positive().safe(), 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>
|
|
|
|
export type CreateBackupRequest = z.infer<typeof createBackupRequestSchema>
|