diff --git a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/SettingsStorageTab.tsx b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/SettingsStorageTab.tsx index 87ad4fff..54fb137a 100644 --- a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/SettingsStorageTab.tsx +++ b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/SettingsStorageTab.tsx @@ -9,6 +9,11 @@ import {ButtonWithLoading} from "@/components/wrappers/Button/ButtonWithLoading/ import {useMutation} from "@tanstack/react-query"; import {checkConnexionToS3} from "@/features/upload/upload.action"; import {toast} from "sonner"; +import {updateUserAction} from "@/components/wrappers/Dashboard/Profile/UserForm/user-form.action"; +import {useRouter} from "next/navigation"; +import { + updateStorageSettingsAction +} from "@/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.action"; export type SettingsStorageTabProps = { @@ -16,19 +21,37 @@ export type SettingsStorageTabProps = { } export const SettingsStorageTab = (props: SettingsStorageTabProps) => { + const router = useRouter() const mutation = useMutation({ mutationFn: async () => { const result = await checkConnexionToS3() if(result.error){ toast.error("An error occured during the connexion !") + }else{ + toast.success("Connexion succeed!") } - toast.success("Connexion succeed!") } }) const [isSwitched, setIsSwitched] = useState(props.settings.storage !== "local"); + const updateMutation = useMutation({ + mutationFn: () => updateStorageSettingsAction({name: "system", data: {storage: isSwitched ? "s3": "local"}}), + onSuccess: () => { + toast.success(`Settings updated successfully.`); + router.refresh() + }, + onError: () => { + toast.error(`An error occurred while updating settings information.`); + }, + }); + + + const HandleSwitchStorage = async () => { + setIsSwitched(!isSwitched); + await updateMutation.mutateAsync() + } return (
@@ -47,8 +70,8 @@ export const SettingsStorageTab = (props: SettingsStorageTabProps) => { { - setIsSwitched(!isSwitched); + onCheckedChange={async () => { + await HandleSwitchStorage() }} id="storage-mode"/>
diff --git a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/StorageS3Form.tsx b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/StorageS3Form.tsx index e2fd93ed..755cde19 100644 --- a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/StorageS3Form.tsx +++ b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/StorageS3Form.tsx @@ -14,6 +14,11 @@ import { S3FormSchema, S3FormType } from "@/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema"; +import {useRouter} from "next/navigation"; +import {toast} from "sonner"; +import { + updateS3SettingsAction +} from "@/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.action"; export type S3FormProps = { defaultValues?: S3FormType; @@ -24,9 +29,21 @@ export const StorageS3Form = (props: S3FormProps) => { schema: S3FormSchema, defaultValues: props.defaultValues, }); + + const router = useRouter(); + const mutation = useMutation({ mutationFn: async (values: S3FormType) => { - + console.log(values) + const updateS3Settings = await updateS3SettingsAction({name: "system", data: values}) + const data = updateS3Settings?.data?.data + if (updateS3Settings?.serverError || !data) { + console.log(updateS3Settings?.serverError); + toast.error(updateS3Settings?.serverError); + return; + } + toast.success(`Success updating storage informations`); + router.refresh() } }) diff --git a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.action.ts b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.action.ts index e69de29b..2c28638e 100644 --- a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.action.ts +++ b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.action.ts @@ -0,0 +1,51 @@ +"use server" +import {userAction} from "@/safe-actions"; +import {z} from "zod"; +import {prisma} from "@/prisma"; +import { + S3FormSchema, + StorageSwitchSchema +} from "@/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema"; + + +export const updateS3SettingsAction = userAction + .schema( + z.object({ + name: z.string(), + data: S3FormSchema, + } + ) + ) + .action(async ({parsedInput, ctx}) => { + const updatedSettings = await prisma.settings.update({ + where: { + name: parsedInput.name, + }, + data: parsedInput.data, + }) + return { + data: updatedSettings, + } + }) + +export const updateStorageSettingsAction = userAction + .schema( + z.object({ + name: z.string(), + data: StorageSwitchSchema, + } + ) + ) + .action(async ({parsedInput, ctx}) => { + const updatedSettings = await prisma.settings.update({ + where: { + name: parsedInput.name, + }, + data: parsedInput.data, + }) + return { + data: updatedSettings, + } + }) + + diff --git a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema.ts b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema.ts index 32017aac..a47fb358 100644 --- a/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema.ts +++ b/src/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema.ts @@ -8,3 +8,10 @@ export const S3FormSchema = z.object({ }); export type S3FormType = z.infer; + + +export const StorageSwitchSchema = z.object({ + storage: z.string(), +}) + +export type StorageType= z.infer; diff --git a/src/features/upload/upload.action.ts b/src/features/upload/upload.action.ts index 52f81209..3e483f2b 100644 --- a/src/features/upload/upload.action.ts +++ b/src/features/upload/upload.action.ts @@ -2,10 +2,13 @@ import {userAction} from "@/safe-actions"; import {z} from "zod"; import {v4 as uuidv4} from 'uuid'; -import { writeFile, access, mkdir } from "fs/promises"; +import {mkdir, writeFile} from "fs/promises"; import path from "path"; import {env} from "@/env.mjs"; -import {checkMinioAlive} from "@/utils/s3-file-management"; +import {checkMinioAlive, createPublicBucket, saveFileInBucket} from "@/utils/s3-file-management"; +import {prisma} from "@/prisma"; +import {UploadedObjectInfo} from "minio/src/internal/type"; +import {Settings} from "@prisma/client"; export const uploadImageAction = userAction @@ -14,17 +17,28 @@ export const uploadImageAction = userAction const file = formData.get("file") as File const uuid = uuidv4() - const fileFormat = file.name.split(".").slice(-1)[0] const fileName = uuid + "." + fileFormat const arrayBuffer = await file.arrayBuffer() const buffer = Buffer.from(arrayBuffer) + const settings = await prisma.settings.findUnique({ + where:{ + name: "system" + } + }) - // await uploadS3Compatible() + let result: void | UploadedObjectInfo; + const bucketName = 'public-image-bucket'; - const result = await uploadLocal(fileName,buffer) - const url = getUrl(fileName) + if(settings.storage === "local") { + result = await uploadLocal(fileName,buffer) + }else if (settings.storage === "s3"){ + result = await uploadS3Compatible(bucketName,fileName, buffer) + } + + const url = getUrl(fileName, settings, bucketName) + console.log(url) return { data: {result: result, url: url}, } @@ -32,35 +46,29 @@ export const uploadImageAction = userAction }); -function getUrl(fileName:string):string { - let url: string = ""; + +function getUrl(fileName:string, settings: Settings, bucketName: string):string { if (env.NODE_ENV === "production") { // url = `https://${env.S3_ENDPOINT}/${bucketName}/${fileName}` - return url + return "url" } else { - url = `http://localhost:8887/uploads/${fileName}` - return url + if(settings.storage === "s3"){ + return `http://localhost:${env.S3_PORT}/${bucketName}/${fileName}` + }else if(settings.storage === "local"){ + return `http://localhost:8887/uploads/${fileName}` + } } } - -async function uploadLocal(fileName: string, buffer: any): Promise<{ data: { result: void; url: string } }> { +async function uploadLocal(fileName: string, buffer: any) { const localDir = "public/uploads/" try { await mkdir(path.join(process.cwd(), localDir), { recursive: true }); - const result = await writeFile( + return await writeFile( path.join(process.cwd(), localDir + fileName), buffer - ); - let url: string = ""; - if (env.NODE_ENV === "production") { - // url = `https://${env.S3_ENDPOINT}/${bucketName}/${fileName}` - } else { - url = `http://localhost:8887/uploads/${fileName}` - } - return { - data: {result: result, url: url}, - } + ) + } catch (error) { console.log("Error occured ", error); throw new Error('An error occured while importing image'); @@ -68,8 +76,13 @@ async function uploadLocal(fileName: string, buffer: any): Promise<{ data: { res } } -async function uploadS3Compatible() { - await checkMinioAlive() +async function uploadS3Compatible(bucketName: string, fileName: string, buffer: any){ + await createPublicBucket({bucketName}); + return await saveFileInBucket({ + bucketName, + fileName, + file: buffer, + }) } export async function checkConnexionToS3(){ diff --git a/src/utils/s3-file-management.ts b/src/utils/s3-file-management.ts index acdf9c7b..4f7975b3 100644 --- a/src/utils/s3-file-management.ts +++ b/src/utils/s3-file-management.ts @@ -3,11 +3,11 @@ import {env} from "@/env.mjs"; import internal from "node:stream"; import {prisma} from "@/prisma"; -const settings = await prisma.settings.findUnique({ - where:{ - name: "system" - } -}) +// const settings = await prisma.settings.findUnique({ +// where:{ +// name: "system" +// } +// }) // Create a new Minio client with the S3 endpoint, access key, and secret key @@ -23,21 +23,47 @@ const settings = await prisma.settings.findUnique({ // secretKey: env.S3_SECRET_KEY ?? "", // useSSL: env.S3_USE_SSL === 'true' // }) -export const s3Client = env.NODE_ENV === "production" ? - new Minio.Client({ - endPoint: settings.s3EndPointUrl ?? "", - accessKey: settings.s3AccessKeyId ?? "", - secretKey: settings.s3SecretAccessKey ?? "", - }) : new Minio.Client({ - endPoint: settings.s3EndPointUrl ?? "", - port: Number(env.S3_PORT ?? 0), - accessKey: settings.s3AccessKeyId ?? "", - secretKey: settings.s3SecretAccessKey ?? "", - useSSL: env.S3_USE_SSL === 'true' +// export const s3Client = env.NODE_ENV === "production" ? +// new Minio.Client({ +// endPoint: settings.s3EndPointUrl ?? "", +// accessKey: settings.s3AccessKeyId ?? "", +// secretKey: settings.s3SecretAccessKey ?? "", +// }) : new Minio.Client({ +// endPoint: settings.s3EndPointUrl ?? "", +// port: Number(env.S3_PORT ?? 0), +// accessKey: settings.s3AccessKeyId ?? "", +// secretKey: settings.s3SecretAccessKey ?? "", +// useSSL: env.S3_USE_SSL === 'true' +// }) + + +async function gets3Client() { + + const settings = await prisma.settings.findUnique({ + where: { + name: "system" + } }) + const s3Client = env.NODE_ENV === "production" ? + new Minio.Client({ + endPoint: settings.s3EndPointUrl ?? "", + accessKey: settings.s3AccessKeyId ?? "", + secretKey: settings.s3SecretAccessKey ?? "", + }) : new Minio.Client({ + endPoint: settings.s3EndPointUrl ?? "", + port: Number(env.S3_PORT ?? 0), + accessKey: settings.s3AccessKeyId ?? "", + secretKey: settings.s3SecretAccessKey ?? "", + useSSL: env.S3_USE_SSL === 'true' + }) + return s3Client +} + export async function checkMinioAlive() { try { + console.log("Check MinioAlive"); + const s3Client = await gets3Client() // Try to list buckets to check connectivity const buckets = await s3Client.listBuckets(); console.log('MinIO is up and running. Buckets:', buckets); @@ -50,6 +76,8 @@ export async function checkMinioAlive() { } export async function createBucketIfNotExists(bucketName: string) { + const s3Client = await gets3Client() + const bucketExists = await s3Client.bucketExists(bucketName) if (!bucketExists) { console.log(`Creating bucket ${bucketName}`); @@ -88,6 +116,7 @@ export async function saveFileInBucket({ if (fileExists) { throw new Error('File already exists') } + const s3Client = await gets3Client() // Upload image to S3 bucket const result = await s3Client.putObject(bucketName, fileName, file) @@ -101,6 +130,8 @@ export async function saveFileInBucket({ * @returns true if file exists, false if not */ export async function checkFileExistsInBucket({bucketName, fileName}: { bucketName: string; fileName: string }) { + const s3Client = await gets3Client() + try { await s3Client.statObject(bucketName, fileName) } catch (error) { @@ -125,6 +156,7 @@ export async function createPresignedUrlToUpload({ }) { // Create bucket if it doesn't exist await createBucketIfNotExists(bucketName) + const s3Client = await gets3Client() return await s3Client.presignedPutObject(bucketName, fileName, expiry) } @@ -132,6 +164,8 @@ export async function createPresignedUrlToUpload({ // Function to create a bucket and make it public export async function createPublicBucket({bucketName}: { bucketName: string }) { + const s3Client = await gets3Client() + try { // Check if the bucket already exists const exists = await s3Client.bucketExists(bucketName);