fix: block if already ongoing or waiting

This commit is contained in:
Charles GTE
2026-07-05 14:06:40 +02:00
parent 503d5469d4
commit ff65590c97
3 changed files with 46 additions and 10 deletions
@@ -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<ServerActionResult<{queued: number}>> => {
.action(async ({parsedInput}): Promise<ServerActionResult<{queued: number; skipped: {databaseId: string; reason: string}[]}>> => {
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 {
@@ -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<RestorePreviewRow[]> {
@@ -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(
@@ -55,7 +55,7 @@ export const BulkRestoreModal = (props: BulkRestoreModalProps) => {
<td className="p-2">
{r.restorable
? <span className="text-green-600">{r.backupDate ? new Date(r.backupDate).toLocaleString() : "latest backup"}</span>
: <span className="text-amber-500">no successful backup skipped</span>}
: <span className="text-amber-500">{r.reason ?? "no successful backup"} skipped</span>}
</td>
</tr>
))}