mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
feat: backup delete/restore actions.
This commit is contained in:
@@ -129,41 +129,7 @@ export async function POST(
|
|||||||
const fileName = `${uuid}${fileExtension}`;
|
const fileName = `${uuid}${fileExtension}`;
|
||||||
const buffer = Buffer.from(await decryptedFile.arrayBuffer());
|
const buffer = Buffer.from(await decryptedFile.arrayBuffer());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// const [settings] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
|
|
||||||
// if (!settings) {
|
|
||||||
// throw new Error("System settings not found.");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let success: boolean, message: string, filePath: string;
|
|
||||||
//
|
|
||||||
// const result =
|
|
||||||
// settings.storage === "local"
|
|
||||||
// ? await uploadLocalPrivate(fileName, buffer)
|
|
||||||
// : await uploadS3Private(`${database.project?.slug}/${fileName}`, buffer, env.S3_BUCKET_NAME!);
|
|
||||||
//
|
|
||||||
// ({success, message, filePath} = result);
|
|
||||||
//
|
|
||||||
// if (!success) {
|
|
||||||
// return NextResponse.json(
|
|
||||||
// {error: message},
|
|
||||||
// {status: 500}
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
await storeBackupFiles(backup, database, buffer, fileName)
|
await storeBackupFiles(backup, database, buffer, fileName)
|
||||||
|
|
||||||
//
|
|
||||||
// await db
|
|
||||||
// .update(drizzleDb.schemas.backup)
|
|
||||||
// .set(withUpdatedAt({
|
|
||||||
// file: fileName,
|
|
||||||
// fileSize: fileSizeBytes,
|
|
||||||
// status: 'success',
|
|
||||||
// }))
|
|
||||||
// .where(eq(drizzleDb.schemas.backup.id, backup.id));
|
|
||||||
|
|
||||||
eventEmitter.emit('modification', {update: true});
|
eventEmitter.emit('modification', {update: true});
|
||||||
|
|
||||||
await sendNotificationsBackupRestore(database, "success_backup");
|
await sendNotificationsBackupRestore(database, "success_backup");
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
|
|||||||
|
|
||||||
if (!restoration.backupStorage || restoration.backupStorage.status != "success" || !restoration.backupStorage.path) {
|
if (!restoration.backupStorage || restoration.backupStorage.status != "success" || !restoration.backupStorage.path) {
|
||||||
restoreAction = false
|
restoreAction = false
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import {BackupWith, Restoration} from "@/db/schema/07_database";
|
import {Backup, BackupWith, Restoration} from "@/db/schema/07_database";
|
||||||
import React, {useState} from "react";
|
import React from "react";
|
||||||
import {Swiper, SwiperSlide} from "swiper/react";
|
import {Swiper, SwiperSlide} from "swiper/react";
|
||||||
|
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/pagination";
|
import "swiper/css/pagination";
|
||||||
|
|
||||||
import {Pagination, Mousewheel} from "swiper/modules";
|
import {Pagination, Mousewheel} from "swiper/modules";
|
||||||
import {DatabaseActionKind, useBackupModal} from "@/components/wrappers/dashboard/database/backup/backup-modal-context";
|
import {DatabaseActionKind, useBackupModal} from "@/components/wrappers/dashboard/database/backup/backup-modal-context";
|
||||||
import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||||
@@ -23,7 +21,7 @@ import {useIsMobile} from "@/hooks/use-mobile";
|
|||||||
import {Badge} from "@/components/ui/badge";
|
import {Badge} from "@/components/ui/badge";
|
||||||
import {getStatusColor, getStatusIcon} from "@/components/wrappers/dashboard/admin/notifications/logs/columns";
|
import {getStatusColor, getStatusIcon} from "@/components/wrappers/dashboard/admin/notifications/logs/columns";
|
||||||
import {
|
import {
|
||||||
createRestorationBackupAction,
|
createRestorationBackupAction, deleteBackupAction, deleteBackupStorageAction,
|
||||||
downloadBackupAction
|
downloadBackupAction
|
||||||
} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions.action";
|
} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions.action";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
@@ -37,6 +35,7 @@ type BackupActionsFormProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const {closeModal} = useBackupModal();
|
const {closeModal} = useBackupModal();
|
||||||
|
|
||||||
@@ -49,8 +48,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
|
|
||||||
let result: SafeActionResult<string, ZodString, readonly [], {
|
let result: SafeActionResult<string, ZodString, readonly [], {
|
||||||
_errors?: string[] | undefined;
|
_errors?: string[] | undefined;
|
||||||
}, readonly [], ServerActionResult<string | Restoration>, object> | undefined
|
}, readonly [], ServerActionResult<string | Restoration | Backup>, object> | undefined
|
||||||
|
|
||||||
|
|
||||||
if (action === "download") {
|
if (action === "download") {
|
||||||
result = await downloadBackupAction({backupStorageId: values.backupStorageId})
|
result = await downloadBackupAction({backupStorageId: values.backupStorageId})
|
||||||
@@ -60,13 +58,18 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
backupStorageId: values.backupStorageId,
|
backupStorageId: values.backupStorageId,
|
||||||
backupId: backup.id
|
backupId: backup.id
|
||||||
})
|
})
|
||||||
|
} else if (action === "delete") {
|
||||||
|
result = await deleteBackupStorageAction({
|
||||||
|
databaseId: backup.databaseId,
|
||||||
|
backupStorageId: values.backupStorageId,
|
||||||
|
backupId: backup.id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const inner = result?.data;
|
const inner = result?.data;
|
||||||
|
|
||||||
if (inner?.success) {
|
if (inner?.success) {
|
||||||
toast.success(inner.actionSuccess?.message);
|
toast.success(inner.actionSuccess?.message);
|
||||||
|
|
||||||
if (action === "download") {
|
if (action === "download") {
|
||||||
console.log(inner.value)
|
console.log(inner.value)
|
||||||
const url = inner.value
|
const url = inner.value
|
||||||
@@ -76,25 +79,45 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
closeModal()
|
closeModal()
|
||||||
} else if (action === "restore") {
|
} else if (action === "restore") {
|
||||||
closeModal()
|
closeModal()
|
||||||
|
} else if (action === "delete") {
|
||||||
|
closeModal()
|
||||||
|
} else {
|
||||||
|
closeModal()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (action === "delete") {
|
||||||
|
toast.success("Backup deleted successfully.")
|
||||||
|
closeModal()
|
||||||
} else {
|
} else {
|
||||||
toast.error(inner?.actionError?.message);
|
toast.error(inner?.actionError?.message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mutationDeleteEntireBackup = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
|
||||||
|
console.log("mutation deleteEntireBackup");
|
||||||
|
|
||||||
|
const result = await deleteBackupAction({
|
||||||
|
databaseId: backup.databaseId,
|
||||||
|
backupId: backup.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
const inner = result?.data;
|
||||||
|
|
||||||
|
if (inner?.success) {
|
||||||
|
toast.success(inner.actionSuccess?.message);
|
||||||
|
closeModal()
|
||||||
|
} else {
|
||||||
|
toast.error(inner?.actionError?.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
|
||||||
|
|
||||||
{action == "delete" ?
|
|
||||||
<>
|
|
||||||
</>
|
|
||||||
:
|
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
className="flex flex-col gap-4 mb-1"
|
className="flex flex-col gap-4 mb-1"
|
||||||
@@ -120,16 +143,17 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
className="mySwiper"
|
className="mySwiper"
|
||||||
style={{height: "100%"}}
|
style={{height: "100%"}}
|
||||||
>
|
>
|
||||||
{backup.storages?.map((storage: BackupStorageWith) => (
|
{backup.storages?.filter((storage) => storage.deletedAt === null).map((storage: BackupStorageWith) => (
|
||||||
<SwiperSlide key={storage.id}>
|
<SwiperSlide key={storage.id}>
|
||||||
<button
|
<button
|
||||||
|
disabled={action !== "delete" && storage.status.toLowerCase() !== "success"}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => field.onChange(storage.id)}
|
onClick={() => field.onChange(storage.id)}
|
||||||
className={`w-full h-full flex items-start gap-3 p-4 rounded-lg border text-left transition-colors ${
|
className={`w-full h-full flex items-start gap-3 p-4 rounded-lg border text-left transition-colors
|
||||||
field.value === storage.id
|
${field.value === storage.id
|
||||||
? "border-foreground bg-background"
|
? "border-foreground bg-background"
|
||||||
: "border-border bg-background hover:border-muted-foreground"
|
: "border-border bg-background" + ((storage.status.toLowerCase() === "success" || action === "delete") ? " hover:border-muted-foreground" : "")}
|
||||||
}`}
|
${storage.status.toLowerCase() !== "success" && action !== "delete" ? "opacity-50 cursor-not-allowed" : ""}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`mt-0.5 h-4 w-4 shrink-0 rounded-full border ${
|
className={`mt-0.5 h-4 w-4 shrink-0 rounded-full border ${
|
||||||
@@ -145,7 +169,9 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{getChannelIcon(storage.storageChannel?.provider || "")}
|
{getChannelIcon(storage.storageChannel?.provider || "")}
|
||||||
<h3 className="font-medium text-foreground">{isMobile ? truncateWords(storage?.storageChannel?.name ?? "", 2) : storage.storageChannel?.name}</h3>
|
<h3 className="font-medium text-foreground">
|
||||||
|
{isMobile ? truncateWords(storage?.storageChannel?.name ?? "", 2) : storage.storageChannel?.name}
|
||||||
|
</h3>
|
||||||
<Badge variant="secondary"
|
<Badge variant="secondary"
|
||||||
className="text-xs font-mono">
|
className="text-xs font-mono">
|
||||||
{storage.storageChannel?.provider}
|
{storage.storageChannel?.provider}
|
||||||
@@ -163,7 +189,6 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
|
|
||||||
)) ?? <p>No storages available</p>}
|
)) ?? <p>No storages available</p>}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,14 +197,29 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col items-center gap-y-6 w-full">
|
<div className="flex flex-row items-center gap-x-4 w-full">
|
||||||
<ButtonWithLoading className="mt-2 w-full" isPending={mutation.isPending}
|
{action === "delete" && (
|
||||||
disabled={mutation.isPending}>
|
<ButtonWithLoading
|
||||||
|
type="button"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => mutationDeleteEntireBackup.mutateAsync()}
|
||||||
|
isPending={mutationDeleteEntireBackup.isPending}
|
||||||
|
disabled={mutationDeleteEntireBackup.isPending}
|
||||||
|
>
|
||||||
|
Delete All
|
||||||
|
</ButtonWithLoading>
|
||||||
|
)}
|
||||||
|
<ButtonWithLoading
|
||||||
|
type="submit"
|
||||||
|
isPending={mutation.isPending}
|
||||||
|
disabled={mutation.isPending}
|
||||||
|
className="ml-auto"
|
||||||
|
>
|
||||||
Confirm
|
Confirm
|
||||||
</ButtonWithLoading>
|
</ButtonWithLoading>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
}
|
|
||||||
|
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ export const DatabaseBackupActionsModal = ({}: DatabaseActionsModalProps) => {
|
|||||||
if (!backup || !action) return null;
|
if (!backup || !action) return null;
|
||||||
const text = getBackupActionTextBasedOnActionKind(action);
|
const text = getBackupActionTextBasedOnActionKind(action);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={closeModal}>
|
<Dialog open={open} onOpenChange={closeModal}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{text} storage backup ?</DialogTitle>
|
<DialogTitle>{text} backup ?</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Select the backup storage you want to {text.toLowerCase()}
|
Select the backup storage
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
<Separator className="mt-3 mb-3"/>
|
<Separator className="mt-3 mb-3"/>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
+217
-2
@@ -5,9 +5,10 @@ import {z} from "zod";
|
|||||||
import type {StorageInput} from "@/features/storages/types";
|
import type {StorageInput} from "@/features/storages/types";
|
||||||
import {dispatchStorage} from "@/features/storages/dispatch";
|
import {dispatchStorage} from "@/features/storages/dispatch";
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
import {eq} from "drizzle-orm";
|
import {and, eq, isNull, ne, sql} from "drizzle-orm";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {Restoration} from "@/db/schema/07_database";
|
import {Backup, Restoration} from "@/db/schema/07_database";
|
||||||
|
import {withUpdatedAt} from "@/db/utils";
|
||||||
|
|
||||||
|
|
||||||
export const downloadBackupAction = userAction.schema(
|
export const downloadBackupAction = userAction.schema(
|
||||||
@@ -120,3 +121,217 @@ export const createRestorationBackupAction = userAction
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const deleteBackupStorageAction = userAction
|
||||||
|
.schema(
|
||||||
|
z.object({
|
||||||
|
backupId: z.string(),
|
||||||
|
databaseId: z.string(),
|
||||||
|
backupStorageId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.action(async ({parsedInput}): Promise<ServerActionResult<Backup>> => {
|
||||||
|
const {backupId, databaseId, backupStorageId} = parsedInput;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const backup = await db.query.backup.findFirst({
|
||||||
|
where: and(eq(drizzleDb.schemas.backup.id, backupId), eq(drizzleDb.schemas.backup.databaseId, databaseId))
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!backup) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Backup not found.",
|
||||||
|
status: 404,
|
||||||
|
messageParams: {backupStorageId: backupStorageId},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const backupStorage = await db.query.backupStorage.findFirst({
|
||||||
|
where: eq(drizzleDb.schemas.backupStorage.id, backupStorageId),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!backupStorage) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Backup storage not found.",
|
||||||
|
status: 404,
|
||||||
|
messageParams: {backupStorageId: backupStorageId},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const [{count}] = await db
|
||||||
|
.select({count: sql<number>`count(*)`})
|
||||||
|
.from(drizzleDb.schemas.backupStorage)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(drizzleDb.schemas.backupStorage.backupId, backupId),
|
||||||
|
isNull(drizzleDb.schemas.backupStorage.deletedAt),
|
||||||
|
ne(drizzleDb.schemas.backupStorage.id, backupStorageId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (Number(count) === 0) {
|
||||||
|
await db
|
||||||
|
.update(drizzleDb.schemas.backup)
|
||||||
|
.set(withUpdatedAt({
|
||||||
|
deletedAt: new Date(),
|
||||||
|
status: backup.status == "ongoing" ? "failed" : backup.status
|
||||||
|
}))
|
||||||
|
.where(and(eq(drizzleDb.schemas.backup.id, backupId), eq(drizzleDb.schemas.backup.databaseId, databaseId)))
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(drizzleDb.schemas.backupStorage)
|
||||||
|
.set(withUpdatedAt({
|
||||||
|
deletedAt: new Date(),
|
||||||
|
}))
|
||||||
|
.where(eq(drizzleDb.schemas.backupStorage.id, backupStorageId))
|
||||||
|
|
||||||
|
|
||||||
|
if (backupStorage.status != "success" || !backupStorage.path) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "An error occurred.",
|
||||||
|
status: 500,
|
||||||
|
messageParams: {backupStorageId: backupStorageId},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const input: StorageInput = {
|
||||||
|
action: "delete",
|
||||||
|
data: {
|
||||||
|
path: backupStorage.path,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await dispatchStorage(input, undefined, backupStorage.storageChannelId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: backup,
|
||||||
|
actionSuccess: {
|
||||||
|
message: `Backup deleted successfully.`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Failed to delete backup.",
|
||||||
|
status: 500,
|
||||||
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
messageParams: {message: "Error deleting the backup"},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const deleteBackupAction = userAction
|
||||||
|
.schema(
|
||||||
|
z.object({
|
||||||
|
backupId: z.string(),
|
||||||
|
databaseId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.action(async ({parsedInput}): Promise<ServerActionResult<Backup>> => {
|
||||||
|
const {backupId, databaseId} = parsedInput;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const backup = await db.query.backup.findFirst({
|
||||||
|
where: and(eq(drizzleDb.schemas.backup.id, backupId), eq(drizzleDb.schemas.backup.databaseId, databaseId))
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!backup) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Backup not found.",
|
||||||
|
status: 404,
|
||||||
|
messageParams: {backupId: backupId},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const backupStorages = await db.query.backupStorage.findMany({
|
||||||
|
where: eq(drizzleDb.schemas.backupStorage.backupId, backupId),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!backupStorages) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Backup storage not found.",
|
||||||
|
status: 404,
|
||||||
|
messageParams: {backupId: backupId},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const backupStorage of backupStorages) {
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(drizzleDb.schemas.backupStorage)
|
||||||
|
.set(withUpdatedAt({
|
||||||
|
deletedAt: new Date(),
|
||||||
|
}))
|
||||||
|
.where(eq(drizzleDb.schemas.backupStorage.id, backupStorage.id))
|
||||||
|
|
||||||
|
if (backupStorage.status != "success" || !backupStorage.path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input: StorageInput = {
|
||||||
|
action: "delete",
|
||||||
|
data: {
|
||||||
|
path: backupStorage.path,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await dispatchStorage(input, undefined, backupStorage.storageChannelId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(drizzleDb.schemas.backup)
|
||||||
|
.set(withUpdatedAt({
|
||||||
|
deletedAt: new Date(),
|
||||||
|
status: backup.status == "ongoing" ? "failed" : backup.status
|
||||||
|
}))
|
||||||
|
.where(and(eq(drizzleDb.schemas.backup.id, backupId), eq(drizzleDb.schemas.backup.databaseId, databaseId)))
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: backup,
|
||||||
|
actionSuccess: {
|
||||||
|
message: `Backup deleted successfully (ref: ${parsedInput.backupId}).`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Failed to delete backup.",
|
||||||
|
status: 500,
|
||||||
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
messageParams: {message: "Error deleting the backup"},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import {createContext, useContext, useState, ReactNode} from "react";
|
import {createContext, useContext, useState, ReactNode} from "react";
|
||||||
import {BackupWith} from "@/db/schema/07_database";
|
import {BackupWith} from "@/db/schema/07_database";
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
|
||||||
export type DatabaseActionKind = "restore" | "download" | "delete";
|
export type DatabaseActionKind = "restore" | "download" | "delete";
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ const BackupModalContext = createContext<BackupModalContextType | undefined>(und
|
|||||||
|
|
||||||
export const BackupModalProvider = ({children}: { children: ReactNode }) => {
|
export const BackupModalProvider = ({children}: { children: ReactNode }) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
const [action, setAction] = useState<DatabaseActionKind | null>(null);
|
const [action, setAction] = useState<DatabaseActionKind | null>(null);
|
||||||
const [backup, setBackup] = useState<BackupWith | null>(null);
|
const [backup, setBackup] = useState<BackupWith | null>(null);
|
||||||
|
|
||||||
@@ -44,6 +46,7 @@ export const BackupModalProvider = ({children}: { children: ReactNode }) => {
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
setAction(null);
|
setAction(null);
|
||||||
setBackup(null);
|
setBackup(null);
|
||||||
|
router.refresh()
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import {useMemo, useState} from "react";
|
|||||||
import {Backup, BackupWith, DatabaseWith} from "@/db/schema/07_database";
|
import {Backup, BackupWith, DatabaseWith} from "@/db/schema/07_database";
|
||||||
import {Setting} from "@/db/schema/01_setting";
|
import {Setting} from "@/db/schema/01_setting";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {deleteBackupAction} from "@/features/dashboard/restore/restore.action";
|
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {MemberWithUser} from "@/db/schema/03_organization";
|
import {MemberWithUser} from "@/db/schema/03_organization";
|
||||||
|
import {deleteBackupAction} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions.action";
|
||||||
|
|
||||||
|
|
||||||
type DatabaseBackupListProps = {
|
type DatabaseBackupListProps = {
|
||||||
@@ -65,13 +65,18 @@ export const DatabaseBackupList = (props: DatabaseBackupListProps) => {
|
|||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
backups.map(async (backup) => {
|
backups.map(async (backup) => {
|
||||||
if (backup.deletedAt == null) {
|
if (backup.deletedAt == null) {
|
||||||
|
|
||||||
const backupDeleted = await deleteBackupAction({
|
const backupDeleted = await deleteBackupAction({
|
||||||
backupId: backup.id,
|
|
||||||
databaseId: backup.databaseId,
|
databaseId: backup.databaseId,
|
||||||
status: backup.status,
|
backupId: backup.id,
|
||||||
file: backup.file ?? "",
|
})
|
||||||
projectSlug: props.database?.project?.slug!
|
// const backupDeleted = await deleteBackupAction({
|
||||||
});
|
// backupId: backup.id,
|
||||||
|
// databaseId: backup.databaseId,
|
||||||
|
// status: backup.status,
|
||||||
|
// file: backup.file ?? "",
|
||||||
|
// projectSlug: props.database?.project?.slug!
|
||||||
|
// });
|
||||||
return {
|
return {
|
||||||
success: backupDeleted?.data?.success,
|
success: backupDeleted?.data?.success,
|
||||||
message: backupDeleted?.data?.success
|
message: backupDeleted?.data?.success
|
||||||
|
|||||||
Reference in New Issue
Block a user