fix: working on adding internal postgres database.

This commit is contained in:
charlesgauthereau
2026-02-15 10:19:28 +01:00
parent b4da6fdd9f
commit 63e83032f1
8 changed files with 42 additions and 588 deletions
@@ -7,12 +7,6 @@ import * as drizzleDb from "@/db";
import {db} from "@/db";
import {and, eq} from "drizzle-orm";
import {Backup, Restoration} from "@/db/schema/07_database";
import {
deleteFileS3Private,
deleteLocalPrivate,
} from "@/features/upload/private/upload.action";
import {env} from "@/env.mjs";
import {withUpdatedAt} from "@/db/utils";
export const deleteRestoreAction = userAction
.schema(
@@ -48,84 +42,6 @@ export const deleteRestoreAction = userAction
}
});
export const deleteBackupAction = userAction
.schema(
z.object({
backupId: z.string(),
databaseId: z.string(),
projectSlug: z.string(),
status: z.enum(["ongoing", "failed", "success", "waiting"]),
file: z.string(),
})
)
.action(async ({parsedInput}): Promise<ServerActionResult<Backup>> => {
try {
const [settings] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
if (!settings) {
return {
success: false,
actionError: {
message: "No settings found.",
status: 404,
cause: "No settings found.",
messageParams: {message: "Error deleting the backup"},
},
};
}
await db
.update(drizzleDb.schemas.backup)
.set(withUpdatedAt({
deletedAt: new Date(),
status: parsedInput.status == "ongoing" ? "failed" : parsedInput.status
}))
.where(and(eq(drizzleDb.schemas.backup.id, parsedInput.backupId), eq(drizzleDb.schemas.backup.databaseId, parsedInput.databaseId)))
let success: boolean, message: string;
if (parsedInput.file) {
const result =
settings.storage === "local"
? await deleteLocalPrivate(parsedInput.file)
: await deleteFileS3Private(`${parsedInput.projectSlug}/${parsedInput.file}`, env.S3_BUCKET_NAME!);
({success, message} = result);
if (!success) {
return {
success: false,
actionError: {
message: message,
status: 404,
cause: "Unable to delete backup from storage",
messageParams: {message: "Error deleting the backup"},
},
};
}
}
return {
success: true,
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"},
},
};
}
});
export const rerunRestorationAction = userAction
.schema(
z.object({
@@ -175,46 +91,3 @@ export const rerunRestorationAction = userAction
};
}
});
export const createRestorationAction = userAction
.schema(
z.object({
backupId: z.string(),
databaseId: z.string(),
})
)
.action(async ({parsedInput}): Promise<ServerActionResult<Restoration>> => {
try {
const restorationData = await db
.insert(drizzleDb.schemas.restoration)
.values({
databaseId: parsedInput.databaseId,
backupId: parsedInput.backupId,
status: "waiting",
})
.returning()
.execute();
const createdRestoration = restorationData[0];
return {
success: true,
value: createdRestoration,
actionSuccess: {
message: "Restoration has been successfully created.",
messageParams: {restorationId: createdRestoration.id},
},
};
} catch (error) {
return {
success: false,
actionError: {
message: "Failed to create restoration.",
status: 500,
cause: error instanceof Error ? error.message : "Unknown error",
messageParams: {message: "Error creating the restoration"},
},
};
}
});
@@ -16,6 +16,7 @@ import {Organization} from "@/db/schema/03_organization";
import {ProjectWith} from "@/db/schema/06_project";
import {GearIcon} from "@radix-ui/react-icons";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
import {useRouter} from "next/navigation";
type ProjectDialogProps = {
databases: DatabaseWith[];
@@ -33,6 +34,7 @@ export const ProjectDialog = ({
isEmpty = false
}: ProjectDialogProps) => {
const [open, setOpen] = useState(false);
const router = useRouter();
return (
<Dialog open={open} onOpenChange={setOpen}>
@@ -60,7 +62,10 @@ export const ProjectDialog = ({
<DialogTitle>{isEdit ? `Edit ${project?.name}` : "Create new project"}</DialogTitle>
</DialogHeader>
<ProjectForm
onSuccess={() => setOpen(false)}
onSuccess={() => {
setOpen(false)
router.refresh()
}}
databases={databases}
organization={organization}
defaultValues={project ? {
@@ -22,11 +22,6 @@ export type projectFormProps = {
};
export const ProjectForm = (props: projectFormProps) => {
const router = useRouter();
const queryClient = useQueryClient();
+4 -1
View File
@@ -74,7 +74,10 @@ export async function dispatchStorage(
};
}
if (channelData) channel = {...channelData, config: channelData.config as Json};
if (channelData) {
// @ts-ignore
channel = {...channelData, config: channelData.config as Json};
}
if (!channel) {
return {
@@ -1,198 +0,0 @@
"use server";
import {mkdir, writeFile} from "fs/promises";
import path from "path";
import * as fs from "node:fs";
import {getServerUrl} from "@/utils/get-server-url";
import {createPresignedUrlToDownload, deleteFileFromBucket, saveFileInBucket} from "@/utils/s3-file-management";
import {env} from "@/env.mjs";
import {z} from "zod";
import {ServerActionResult} from "@/types/action-type";
import {unlink} from "fs/promises";
import {action} from "@/lib/safe-actions/actions";
const privateLocalDir = "private/uploads/files/";
const privateS3Dir = "backups/";
export async function uploadLocalPrivate(fileName: string, buffer: any) {
try {
const privatePath = path.join(env.PRIVATE_PATH!, '/keys/master_key.bin')
await mkdir(path.join(process.cwd(), privateLocalDir), {recursive: true});
await writeFile(path.join(process.cwd(), privateLocalDir, fileName), buffer);
return {
success: true,
message: "File uploaded successfully",
filePath: path.join(privateLocalDir, fileName),
};
} catch (error) {
console.error("Error occurred:", error);
throw new Error("An error occurred while importing the private file");
}
}
export async function uploadS3Private(fileName: string, buffer: any, bucketName: string) {
try {
await saveFileInBucket({
bucketName,
fileName: `${privateS3Dir}${fileName}`,
file: buffer,
});
return {
success: true,
filePath: `${privateS3Dir}${fileName}`,
message: "File uploaded successfully",
};
} catch (error) {
console.error("Error occurred:", error);
throw new Error("An error occurred while importing the private file");
}
}
export async function deleteFileS3Private(fileName: string, bucketName: string) {
try {
await deleteFileFromBucket({
bucketName,
fileName: `${privateS3Dir}${fileName}`,
});
return {
success: true,
message: "File deleted successfully",
};
} catch (error) {
console.error("Error occurred:", error);
throw new Error("An error occurred while deleting the private file");
}
}
/**
* Delete a file from local private storage
*/
export async function deleteLocalPrivate(fileName: string) {
try {
const filePath = path.join(process.cwd(), privateLocalDir, fileName);
await unlink(filePath);
return {
success: true,
message: `File '${fileName}' deleted successfully`,
};
} catch (error: any) {
if (error.code === "ENOENT") {
return {
success: false,
message: `File '${fileName}' not found`,
};
}
console.error("Error occurred while deleting file:", error);
throw new Error("An error occurred while deleting the file");
}
}
export async function getFileUrlPresignedS3(fileName: string) {
try {
return await createPresignedUrlToDownload({
bucketName: env.S3_BUCKET_NAME!,
fileName: fileName,
});
} catch (error) {
throw error;
}
}
export const getFileUrlPresignedLocal = action
.schema(z.object({
dir: z.string().optional(),
fileName: z.string()
}))
.action(async ({parsedInput}): Promise<ServerActionResult<string>> => {
try {
const filePath = path.join(parsedInput.dir ? parsedInput.dir : privateLocalDir, parsedInput.fileName);
await mkdir(path.join(process.cwd(), privateLocalDir), {recursive: true});
if (!fs.existsSync(filePath)) {
console.error("File not found at:", filePath);
throw new Error(`File not found at: ${filePath}`);
}
const crypto = require("crypto");
const baseUrl = getServerUrl();
const expiresAt = Date.now() + 60 * 1000; // expires in 1 minute
const token = crypto.createHash("sha256").update(`${parsedInput.fileName}${expiresAt}`).digest("hex");
return {
success: true,
value: `${baseUrl}/api/files/${parsedInput.fileName}?token=${token}&expires=${expiresAt}`,
actionSuccess: {
message: "Successfully retrieved presigned URL Local",
messageParams: {fileName: parsedInput.fileName},
},
};
} catch (error) {
return {
success: false,
actionError: {
message: "Failed to generate presigned URL",
status: 500,
cause: error instanceof Error ? error.message : "Unknown error",
messageParams: {fileName: parsedInput.fileName},
},
};
}
});
export const getFileUrlPreSignedS3Action = action
.schema(z.string())
.action(async ({parsedInput}): Promise<ServerActionResult<string>> => {
try {
const data = await createPresignedUrlToDownload({
bucketName: env.S3_BUCKET_NAME!,
fileName: parsedInput,
});
return {
success: true,
value: data.url,
actionSuccess: {
message: "Successfully retrieved presigned URL",
messageParams: {fileName: parsedInput},
},
};
} catch (error) {
const isNotFound = error instanceof Error && error.message.includes("File does not exist");
const logContext = {
file: parsedInput,
reason: error instanceof Error ? error.message : "Unknown",
};
console.error("Presigned URL generation failed:", logContext);
return {
success: false,
actionError: {
message: isNotFound
? "File not found in S3 bucket"
: "Failed to generate presigned URL",
status: isNotFound ? 404 : 500,
cause: error instanceof Error ? error.message : "Unknown error",
messageParams: {fileName: parsedInput},
},
};
}
});