mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Upload to s3 for public image working.
This commit is contained in:
+26
-3
@@ -9,6 +9,11 @@ import {ButtonWithLoading} from "@/components/wrappers/Button/ButtonWithLoading/
|
|||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {checkConnexionToS3} from "@/features/upload/upload.action";
|
import {checkConnexionToS3} from "@/features/upload/upload.action";
|
||||||
import {toast} from "sonner";
|
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 = {
|
export type SettingsStorageTabProps = {
|
||||||
@@ -16,19 +21,37 @@ export type SettingsStorageTabProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsStorageTab = (props: SettingsStorageTabProps) => {
|
export const SettingsStorageTab = (props: SettingsStorageTabProps) => {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const result = await checkConnexionToS3()
|
const result = await checkConnexionToS3()
|
||||||
if(result.error){
|
if(result.error){
|
||||||
toast.error("An error occured during the connexion !")
|
toast.error("An error occured during the connexion !")
|
||||||
|
}else{
|
||||||
|
toast.success("Connexion succeed!")
|
||||||
}
|
}
|
||||||
toast.success("Connexion succeed!")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const [isSwitched, setIsSwitched] = useState<boolean>(props.settings.storage !== "local");
|
const [isSwitched, setIsSwitched] = useState<boolean>(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 (
|
return (
|
||||||
<div className="flex flex-col h-full py-4">
|
<div className="flex flex-col h-full py-4">
|
||||||
@@ -47,8 +70,8 @@ export const SettingsStorageTab = (props: SettingsStorageTabProps) => {
|
|||||||
<Label htmlFor="storage-mode">Storage Mode (Local/s3 compatible)</Label>
|
<Label htmlFor="storage-mode">Storage Mode (Local/s3 compatible)</Label>
|
||||||
<Switch
|
<Switch
|
||||||
checked={isSwitched}
|
checked={isSwitched}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={async () => {
|
||||||
setIsSwitched(!isSwitched);
|
await HandleSwitchStorage()
|
||||||
}}
|
}}
|
||||||
id="storage-mode"/>
|
id="storage-mode"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+18
-1
@@ -14,6 +14,11 @@ import {
|
|||||||
S3FormSchema,
|
S3FormSchema,
|
||||||
S3FormType
|
S3FormType
|
||||||
} from "@/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema";
|
} 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 = {
|
export type S3FormProps = {
|
||||||
defaultValues?: S3FormType;
|
defaultValues?: S3FormType;
|
||||||
@@ -24,9 +29,21 @@ export const StorageS3Form = (props: S3FormProps) => {
|
|||||||
schema: S3FormSchema,
|
schema: S3FormSchema,
|
||||||
defaultValues: props.defaultValues,
|
defaultValues: props.defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: async (values: S3FormType) => {
|
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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+51
@@ -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,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+7
@@ -8,3 +8,10 @@ export const S3FormSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export type S3FormType = z.infer<typeof S3FormSchema>;
|
export type S3FormType = z.infer<typeof S3FormSchema>;
|
||||||
|
|
||||||
|
|
||||||
|
export const StorageSwitchSchema = z.object({
|
||||||
|
storage: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type StorageType= z.infer<typeof StorageSwitchSchema>;
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
import {userAction} from "@/safe-actions";
|
import {userAction} from "@/safe-actions";
|
||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
import {v4 as uuidv4} from 'uuid';
|
import {v4 as uuidv4} from 'uuid';
|
||||||
import { writeFile, access, mkdir } from "fs/promises";
|
import {mkdir, writeFile} from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {env} from "@/env.mjs";
|
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
|
export const uploadImageAction = userAction
|
||||||
@@ -14,17 +17,28 @@ export const uploadImageAction = userAction
|
|||||||
|
|
||||||
const file = formData.get("file") as File
|
const file = formData.get("file") as File
|
||||||
const uuid = uuidv4()
|
const uuid = uuidv4()
|
||||||
|
|
||||||
const fileFormat = file.name.split(".").slice(-1)[0]
|
const fileFormat = file.name.split(".").slice(-1)[0]
|
||||||
const fileName = uuid + "." + fileFormat
|
const fileName = uuid + "." + fileFormat
|
||||||
const arrayBuffer = await file.arrayBuffer()
|
const arrayBuffer = await file.arrayBuffer()
|
||||||
const buffer = Buffer.from(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)
|
if(settings.storage === "local") {
|
||||||
const url = getUrl(fileName)
|
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 {
|
return {
|
||||||
data: {result: result, url: url},
|
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") {
|
if (env.NODE_ENV === "production") {
|
||||||
// url = `https://${env.S3_ENDPOINT}/${bucketName}/${fileName}`
|
// url = `https://${env.S3_ENDPOINT}/${bucketName}/${fileName}`
|
||||||
return url
|
return "url"
|
||||||
} else {
|
} else {
|
||||||
url = `http://localhost:8887/uploads/${fileName}`
|
if(settings.storage === "s3"){
|
||||||
return url
|
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) {
|
||||||
async function uploadLocal(fileName: string, buffer: any): Promise<{ data: { result: void; url: string } }> {
|
|
||||||
const localDir = "public/uploads/"
|
const localDir = "public/uploads/"
|
||||||
try {
|
try {
|
||||||
await mkdir(path.join(process.cwd(), localDir), { recursive: true });
|
await mkdir(path.join(process.cwd(), localDir), { recursive: true });
|
||||||
const result = await writeFile(
|
return await writeFile(
|
||||||
path.join(process.cwd(), localDir + fileName),
|
path.join(process.cwd(), localDir + fileName),
|
||||||
buffer
|
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) {
|
} catch (error) {
|
||||||
console.log("Error occured ", error);
|
console.log("Error occured ", error);
|
||||||
throw new Error('An error occured while importing image');
|
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() {
|
async function uploadS3Compatible(bucketName: string, fileName: string, buffer: any){
|
||||||
await checkMinioAlive()
|
await createPublicBucket({bucketName});
|
||||||
|
return await saveFileInBucket({
|
||||||
|
bucketName,
|
||||||
|
fileName,
|
||||||
|
file: buffer,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkConnexionToS3(){
|
export async function checkConnexionToS3(){
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import {env} from "@/env.mjs";
|
|||||||
import internal from "node:stream";
|
import internal from "node:stream";
|
||||||
import {prisma} from "@/prisma";
|
import {prisma} from "@/prisma";
|
||||||
|
|
||||||
const settings = await prisma.settings.findUnique({
|
// const settings = await prisma.settings.findUnique({
|
||||||
where:{
|
// where:{
|
||||||
name: "system"
|
// name: "system"
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
|
|
||||||
// Create a new Minio client with the S3 endpoint, access key, and secret key
|
// 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 ?? "",
|
// secretKey: env.S3_SECRET_KEY ?? "",
|
||||||
// useSSL: env.S3_USE_SSL === 'true'
|
// useSSL: env.S3_USE_SSL === 'true'
|
||||||
// })
|
// })
|
||||||
export const s3Client = env.NODE_ENV === "production" ?
|
// export const s3Client = env.NODE_ENV === "production" ?
|
||||||
new Minio.Client({
|
// new Minio.Client({
|
||||||
endPoint: settings.s3EndPointUrl ?? "",
|
// endPoint: settings.s3EndPointUrl ?? "",
|
||||||
accessKey: settings.s3AccessKeyId ?? "",
|
// accessKey: settings.s3AccessKeyId ?? "",
|
||||||
secretKey: settings.s3SecretAccessKey ?? "",
|
// secretKey: settings.s3SecretAccessKey ?? "",
|
||||||
}) : new Minio.Client({
|
// }) : new Minio.Client({
|
||||||
endPoint: settings.s3EndPointUrl ?? "",
|
// endPoint: settings.s3EndPointUrl ?? "",
|
||||||
port: Number(env.S3_PORT ?? 0),
|
// port: Number(env.S3_PORT ?? 0),
|
||||||
accessKey: settings.s3AccessKeyId ?? "",
|
// accessKey: settings.s3AccessKeyId ?? "",
|
||||||
secretKey: settings.s3SecretAccessKey ?? "",
|
// secretKey: settings.s3SecretAccessKey ?? "",
|
||||||
useSSL: env.S3_USE_SSL === 'true'
|
// 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() {
|
export async function checkMinioAlive() {
|
||||||
try {
|
try {
|
||||||
|
console.log("Check MinioAlive");
|
||||||
|
const s3Client = await gets3Client()
|
||||||
// Try to list buckets to check connectivity
|
// Try to list buckets to check connectivity
|
||||||
const buckets = await s3Client.listBuckets();
|
const buckets = await s3Client.listBuckets();
|
||||||
console.log('MinIO is up and running. Buckets:', buckets);
|
console.log('MinIO is up and running. Buckets:', buckets);
|
||||||
@@ -50,6 +76,8 @@ export async function checkMinioAlive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createBucketIfNotExists(bucketName: string) {
|
export async function createBucketIfNotExists(bucketName: string) {
|
||||||
|
const s3Client = await gets3Client()
|
||||||
|
|
||||||
const bucketExists = await s3Client.bucketExists(bucketName)
|
const bucketExists = await s3Client.bucketExists(bucketName)
|
||||||
if (!bucketExists) {
|
if (!bucketExists) {
|
||||||
console.log(`Creating bucket ${bucketName}`);
|
console.log(`Creating bucket ${bucketName}`);
|
||||||
@@ -88,6 +116,7 @@ export async function saveFileInBucket({
|
|||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
throw new Error('File already exists')
|
throw new Error('File already exists')
|
||||||
}
|
}
|
||||||
|
const s3Client = await gets3Client()
|
||||||
|
|
||||||
// Upload image to S3 bucket
|
// Upload image to S3 bucket
|
||||||
const result = await s3Client.putObject(bucketName, fileName, file)
|
const result = await s3Client.putObject(bucketName, fileName, file)
|
||||||
@@ -101,6 +130,8 @@ export async function saveFileInBucket({
|
|||||||
* @returns true if file exists, false if not
|
* @returns true if file exists, false if not
|
||||||
*/
|
*/
|
||||||
export async function checkFileExistsInBucket({bucketName, fileName}: { bucketName: string; fileName: string }) {
|
export async function checkFileExistsInBucket({bucketName, fileName}: { bucketName: string; fileName: string }) {
|
||||||
|
const s3Client = await gets3Client()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await s3Client.statObject(bucketName, fileName)
|
await s3Client.statObject(bucketName, fileName)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -125,6 +156,7 @@ export async function createPresignedUrlToUpload({
|
|||||||
}) {
|
}) {
|
||||||
// Create bucket if it doesn't exist
|
// Create bucket if it doesn't exist
|
||||||
await createBucketIfNotExists(bucketName)
|
await createBucketIfNotExists(bucketName)
|
||||||
|
const s3Client = await gets3Client()
|
||||||
|
|
||||||
return await s3Client.presignedPutObject(bucketName, fileName, expiry)
|
return await s3Client.presignedPutObject(bucketName, fileName, expiry)
|
||||||
}
|
}
|
||||||
@@ -132,6 +164,8 @@ export async function createPresignedUrlToUpload({
|
|||||||
|
|
||||||
// Function to create a bucket and make it public
|
// Function to create a bucket and make it public
|
||||||
export async function createPublicBucket({bucketName}: { bucketName: string }) {
|
export async function createPublicBucket({bucketName}: { bucketName: string }) {
|
||||||
|
const s3Client = await gets3Client()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if the bucket already exists
|
// Check if the bucket already exists
|
||||||
const exists = await s3Client.bucketExists(bucketName);
|
const exists = await s3Client.bucketExists(bucketName);
|
||||||
|
|||||||
Reference in New Issue
Block a user