mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: block if already ongoing or waiting
This commit is contained in:
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user