diff --git a/src/features/database/actions/bulk-backup.action.ts b/src/features/database/actions/bulk-backup.action.ts index 57d0d333..9abb6a21 100644 --- a/src/features/database/actions/bulk-backup.action.ts +++ b/src/features/database/actions/bulk-backup.action.ts @@ -1,6 +1,7 @@ "use server"; import {z} from "zod"; +import {and, inArray, isNull} from "drizzle-orm"; import {db} from "@/db"; import * as drizzleDb from "@/db"; import {ServerActionResult} from "@/types/action-type"; @@ -14,17 +15,35 @@ const bulkSchema = z.object({ export const bulkBackupAction = userAction .schema(bulkSchema) - .action(async ({parsedInput}): Promise> => { + .action(async ({parsedInput}): Promise> => { try { const {projectId, databaseIds} = parsedInput; await assertDatabasesInOrgProject(projectId, databaseIds); - await db - .insert(drizzleDb.schemas.backup) - .values(databaseIds.map((databaseId) => ({databaseId, status: "waiting" as const}))); + + const activeBackups = await db.query.backup.findMany({ + where: and( + inArray(drizzleDb.schemas.backup.databaseId, databaseIds), + inArray(drizzleDb.schemas.backup.status, ["waiting", "ongoing"]), + isNull(drizzleDb.schemas.backup.deletedAt), + ), + columns: {databaseId: true}, + }); + const busy = new Set(activeBackups.map((b) => b.databaseId)); + const toQueue = databaseIds.filter((id) => !busy.has(id)); + const skipped = [...busy].map((databaseId) => ({databaseId, reason: "backup already in progress"})); + + if (toQueue.length > 0) { + await db + .insert(drizzleDb.schemas.backup) + .values(toQueue.map((databaseId) => ({databaseId, status: "waiting" as const}))); + } + return { success: true, - value: {queued: databaseIds.length}, - actionSuccess: {message: `Queued ${databaseIds.length} backup(s).`}, + value: {queued: toQueue.length, skipped}, + actionSuccess: { + message: `Queued ${toQueue.length} backup(s)${skipped.length ? `, ${skipped.length} skipped` : ""}.`, + }, }; } catch (error) { return { diff --git a/src/features/database/actions/bulk-restore.action.ts b/src/features/database/actions/bulk-restore.action.ts index 9f7e6717..22a16c24 100644 --- a/src/features/database/actions/bulk-restore.action.ts +++ b/src/features/database/actions/bulk-restore.action.ts @@ -1,7 +1,7 @@ "use server"; import {z} from "zod"; -import {and, desc, eq, isNull} from "drizzle-orm"; +import {and, desc, eq, inArray, isNull} from "drizzle-orm"; import {db} from "@/db"; import * as drizzleDb from "@/db"; import {ServerActionResult} from "@/types/action-type"; @@ -34,6 +34,7 @@ export type RestorePreviewRow = { backupStorageId?: string; backupDate?: string; restorable: boolean; + reason?: string; }; async function resolveLatestRestorable(projectId: string, databaseIds: string[]): Promise { @@ -45,6 +46,22 @@ async function resolveLatestRestorable(projectId: string, databaseIds: string[]) const rows: RestorePreviewRow[] = []; for (const databaseId of databaseIds) { + const activeRestore = await db.query.restoration.findFirst({ + where: and( + eq(drizzleDb.schemas.restoration.databaseId, databaseId), + inArray(drizzleDb.schemas.restoration.status, ["waiting", "ongoing"]), + ), + }); + if (activeRestore) { + rows.push({ + databaseId, + name: nameById.get(databaseId) ?? databaseId, + restorable: false, + reason: "restore already in progress", + }); + continue; + } + const backups = await db.query.backup.findMany({ where: and( eq(drizzleDb.schemas.backup.databaseId, databaseId), @@ -76,7 +93,7 @@ async function resolveLatestRestorable(projectId: string, databaseIds: string[]) } } - rows.push(picked ?? {databaseId, name: nameById.get(databaseId) ?? databaseId, restorable: false}); + rows.push(picked ?? {databaseId, name: nameById.get(databaseId) ?? databaseId, restorable: false, reason: "no successful backup"}); } return rows; } @@ -117,7 +134,7 @@ export const bulkRestoreLatestAction = userAction const restorable = rows.filter((r) => r.restorable); const skipped = rows .filter((r) => !r.restorable) - .map((r) => ({databaseId: r.databaseId, reason: "no successful backup"})); + .map((r) => ({databaseId: r.databaseId, reason: r.reason ?? "no successful backup"})); if (restorable.length > 0) { await db.insert(drizzleDb.schemas.restoration).values( diff --git a/src/features/projects/components/bulk-restore-modal.tsx b/src/features/projects/components/bulk-restore-modal.tsx index e2498fa0..6e485e43 100644 --- a/src/features/projects/components/bulk-restore-modal.tsx +++ b/src/features/projects/components/bulk-restore-modal.tsx @@ -55,7 +55,7 @@ export const BulkRestoreModal = (props: BulkRestoreModalProps) => { {r.restorable ? {r.backupDate ? new Date(r.backupDate).toLocaleString() : "latest backup"} - : no successful backup — skipped} + : {r.reason ?? "no successful backup"} — skipped} ))}