fix: refactoring of storage method

This commit is contained in:
charlesgauthereau
2026-01-24 13:14:38 +01:00
parent a64a80910d
commit b2c19e7ed0
6 changed files with 77 additions and 31 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ services:
# context: . # context: .
# dockerfile: docker/dockerfile/Dockerfile # dockerfile: docker/dockerfile/Dockerfile
# target: prod # target: prod
image: solucetechnologies/portabase:1.2.2-rc.2 image: solucetechnologies/portabase:latest
ports: ports:
- '8887:80' - '8887:80'
environment: environment:
+1
View File
@@ -87,6 +87,7 @@
"react-twc": "^1.4.2", "react-twc": "^1.4.2",
"react-use-measure": "^2.1.7", "react-use-measure": "^2.1.7",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"sharp": "^0.34.5",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"sonner": "^2.0.3", "sonner": "^2.0.3",
+20 -18
View File
@@ -124,7 +124,7 @@ importers:
version: 6.0.0 version: 6.0.0
better-auth: better-auth:
specifier: 1.4.5 specifier: 1.4.5
version: 1.4.5(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 1.4.5(next@16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@@ -169,10 +169,10 @@ importers:
version: 12.24.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 12.24.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next: next:
specifier: 16.0.10 specifier: 16.0.10
version: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next-safe-action: next-safe-action:
specifier: ^7.10.8 specifier: ^7.10.8
version: 7.10.8(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.25.76) version: 7.10.8(next@16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.25.76)
next-themes: next-themes:
specifier: ^0.4.6 specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -227,6 +227,9 @@ importers:
recharts: recharts:
specifier: ^2.15.3 specifier: ^2.15.3
version: 2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
sharp:
specifier: ^0.34.5
version: 0.34.5
socket.io: socket.io:
specifier: ^4.8.1 specifier: ^4.8.1
version: 4.8.3 version: 4.8.3
@@ -10133,7 +10136,7 @@ snapshots:
dependencies: dependencies:
'@types/node': 22.19.3 '@types/node': 22.19.3
tapable: 2.3.0 tapable: 2.3.0
webpack: 5.104.1(esbuild@0.27.2) webpack: 5.104.1(esbuild@0.25.10)
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/core' - '@swc/core'
- esbuild - esbuild
@@ -10730,7 +10733,7 @@ snapshots:
bcryptjs@2.4.3: {} bcryptjs@2.4.3: {}
better-auth@1.4.5(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): better-auth@1.4.5(next@16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies: dependencies:
'@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0) '@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)
'@better-auth/telemetry': 1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)) '@better-auth/telemetry': 1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0))
@@ -10746,7 +10749,7 @@ snapshots:
nanostores: 1.1.0 nanostores: 1.1.0
zod: 4.3.5 zod: 4.3.5
optionalDependencies: optionalDependencies:
next: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: 16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3 react: 19.2.3
react-dom: 19.2.3(react@19.2.3) react-dom: 19.2.3(react@19.2.3)
@@ -12660,9 +12663,9 @@ snapshots:
neo-async@2.6.2: {} neo-async@2.6.2: {}
next-safe-action@7.10.8(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.25.76): next-safe-action@7.10.8(next@16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.25.76):
dependencies: dependencies:
next: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: 16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3 react: 19.2.3
react-dom: 19.2.3(react@19.2.3) react-dom: 19.2.3(react@19.2.3)
optionalDependencies: optionalDependencies:
@@ -12691,12 +12694,12 @@ snapshots:
'@next/swc-linux-x64-musl': 15.5.2 '@next/swc-linux-x64-musl': 15.5.2
'@next/swc-win32-arm64-msvc': 15.5.2 '@next/swc-win32-arm64-msvc': 15.5.2
'@next/swc-win32-x64-msvc': 15.5.2 '@next/swc-win32-x64-msvc': 15.5.2
sharp: 0.34.4 sharp: 0.34.5
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): next@16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies: dependencies:
'@next/env': 16.0.10 '@next/env': 16.0.10
'@swc/helpers': 0.5.15 '@swc/helpers': 0.5.15
@@ -12704,7 +12707,7 @@ snapshots:
postcss: 8.4.31 postcss: 8.4.31
react: 19.2.3 react: 19.2.3
react-dom: 19.2.3(react@19.2.3) react-dom: 19.2.3(react@19.2.3)
styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.3) styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.2.3)
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 16.0.10 '@next/swc-darwin-arm64': 16.0.10
'@next/swc-darwin-x64': 16.0.10 '@next/swc-darwin-x64': 16.0.10
@@ -13531,7 +13534,6 @@ snapshots:
'@img/sharp-win32-arm64': 0.34.5 '@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5 '@img/sharp-win32-x64': 0.34.5
optional: true
shebang-command@2.0.0: shebang-command@2.0.0:
dependencies: dependencies:
@@ -13785,12 +13787,12 @@ snapshots:
optionalDependencies: optionalDependencies:
'@babel/core': 7.26.10 '@babel/core': 7.26.10
styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.3): styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.2.3):
dependencies: dependencies:
client-only: 0.0.1 client-only: 0.0.1
react: 19.2.3 react: 19.2.3
optionalDependencies: optionalDependencies:
'@babel/core': 7.28.5 '@babel/core': 7.26.10
sucrase@3.35.1: sucrase@3.35.1:
dependencies: dependencies:
@@ -13878,14 +13880,14 @@ snapshots:
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
supports-hyperlinks: 2.3.0 supports-hyperlinks: 2.3.0
terser-webpack-plugin@5.3.16(esbuild@0.25.10)(webpack@5.104.1(esbuild@0.25.10)): terser-webpack-plugin@5.3.16(esbuild@0.25.10)(webpack@5.104.1(esbuild@0.27.2)):
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1 jest-worker: 27.5.1
schema-utils: 4.3.3 schema-utils: 4.3.3
serialize-javascript: 6.0.2 serialize-javascript: 6.0.2
terser: 5.44.1 terser: 5.44.1
webpack: 5.104.1(esbuild@0.27.2) webpack: 5.104.1(esbuild@0.25.10)
optionalDependencies: optionalDependencies:
esbuild: 0.25.10 esbuild: 0.25.10
@@ -14240,7 +14242,7 @@ snapshots:
webpack-sources@3.3.3: {} webpack-sources@3.3.3: {}
webpack@5.104.1(esbuild@0.27.2): webpack@5.104.1(esbuild@0.25.10):
dependencies: dependencies:
'@types/eslint-scope': 3.7.7 '@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8 '@types/estree': 1.0.8
@@ -14264,7 +14266,7 @@ snapshots:
neo-async: 2.6.2 neo-async: 2.6.2
schema-utils: 4.3.3 schema-utils: 4.3.3
tapable: 2.3.0 tapable: 2.3.0
terser-webpack-plugin: 5.3.16(esbuild@0.25.10)(webpack@5.104.1(esbuild@0.25.10)) terser-webpack-plugin: 5.3.16(esbuild@0.25.10)(webpack@5.104.1(esbuild@0.27.2))
watchpack: 2.5.0 watchpack: 2.5.0
webpack-sources: 3.3.3 webpack-sources: 3.3.3
transitivePeerDependencies: transitivePeerDependencies:
@@ -48,14 +48,25 @@ export const AvatarWithUpload = (props: AvatarWithUploadProps) => {
const handleImageUpload = async (event: ChangeEvent<HTMLInputElement>) => { const handleImageUpload = async (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (!file) return; if (!file) return;
if (!file.type.includes("image")) {
toast.error("File not an image"); const allowedFormats = ["image/jpeg", "image/png", "image/webp"];
if (!allowedFormats.includes(file.type)) {
toast.error("Only JPG, PNG or WebP images are allowed.");
return; return;
} }
const maxSizeInMB = 5;
if (file.size > maxSizeInMB * 1024 * 1024) {
toast.error(`Image must be smaller than ${maxSizeInMB}MB.`);
return;
}
submitImage.mutate(file); submitImage.mutate(file);
}; };
return ( return (
<div className="relative "> <div className="relative ">
@@ -68,7 +79,7 @@ export const AvatarWithUpload = (props: AvatarWithUploadProps) => {
onClick={() => { onClick={() => {
const fileInput = document.createElement("input"); const fileInput = document.createElement("input");
fileInput.type = "file"; fileInput.type = "file";
fileInput.accept = "image/*"; fileInput.accept = ".jpg,.jpeg,.png,.webp";
// @ts-ignore // @ts-ignore
fileInput.onchange = handleImageUpload; fileInput.onchange = handleImageUpload;
fileInput.click(); fileInput.click();
+26 -8
View File
@@ -1,6 +1,6 @@
import * as Minio from "minio"; import * as Minio from "minio";
import {StorageDeleteInput, StorageGetInput, StorageResult, StorageUploadInput} from "../types"; import {StorageDeleteInput, StorageGetInput, StorageMetaData, StorageResult, StorageUploadInput} from "../types";
import {getServerUrl} from "@/utils/get-server-url"; import {generateFileUrl} from "@/features/storages/helpers";
type S3Config = { type S3Config = {
endPointUrl: string; endPointUrl: string;
@@ -30,7 +30,10 @@ async function ensureBucket(config: S3Config) {
if (!exists) await client.makeBucket(config.bucketName); if (!exists) await client.makeBucket(config.bucketName);
} }
export async function uploadS3(config: S3Config, input: { data: StorageUploadInput }): Promise<StorageResult> { export async function uploadS3(
config: S3Config,
input: { data: StorageUploadInput, metadata?: StorageMetaData }
): Promise<StorageResult> {
const client = await getS3Client(config); const client = await getS3Client(config);
await ensureBucket(config); await ensureBucket(config);
@@ -44,16 +47,32 @@ export async function uploadS3(config: S3Config, input: { data: StorageUploadInp
} }
await client.putObject(config.bucketName, key, input.data.file as Buffer); await client.putObject(config.bucketName, key, input.data.file as Buffer);
const baseUrl = getServerUrl();
if (input.data.url) {
const url = await generateFileUrl(input);
if (!url) {
return {
success: false,
provider: "s3",
response: "Unable to get url file"
};
}
return {
success: true,
provider: 's3',
url: url
};
}
return { return {
success: true, success: true,
provider: 's3', provider: 's3',
url: `${baseUrl}/api/${input.data.path}`,
}; };
} }
export async function getS3(config: S3Config, input: { data: StorageGetInput }): Promise<StorageResult> { export async function getS3(config: S3Config, input: { data: StorageGetInput, metadata: StorageMetaData }): Promise<StorageResult> {
const client = await getS3Client(config); const client = await getS3Client(config);
const key = `${BASE_DIR}${input.data.path}`; const key = `${BASE_DIR}${input.data.path}`;
@@ -78,9 +97,8 @@ export async function getS3(config: S3Config, input: { data: StorageGetInput }):
}; };
} }
export async function deleteS3(config: S3Config, input: { data: StorageDeleteInput }): Promise<StorageResult> { export async function deleteS3(config: S3Config, input: { data: StorageDeleteInput, metadata?: StorageMetaData }): Promise<StorageResult> {
const client = await getS3Client(config); const client = await getS3Client(config);
// const key = input.data.path;
const key = `${BASE_DIR}${input.data.path}`; const key = `${BASE_DIR}${input.data.path}`;
try { try {
+15 -1
View File
@@ -8,6 +8,7 @@ import * as drizzleDb from "@/db";
import {ServerActionResult} from "@/types/action-type"; import {ServerActionResult} from "@/types/action-type";
import {dispatchStorage} from "@/features/storages/dispatch"; import {dispatchStorage} from "@/features/storages/dispatch";
import {StorageInput} from "@/features/storages/types"; import {StorageInput} from "@/features/storages/types";
import sharp from "sharp";
export const uploadUserImageAction = userAction.schema( export const uploadUserImageAction = userAction.schema(
@@ -22,6 +23,19 @@ export const uploadUserImageAction = userAction.schema(
const buffer = Buffer.from(arrayBuffer); const buffer = Buffer.from(arrayBuffer);
const isPng = fileFormat === "png";
const isWebp = fileFormat === "webp";
const compressedBuffer = await sharp(buffer)
.resize({ width: 1024 })
.toFormat(isPng ? "png" : isWebp ? "webp" : "jpeg", {
quality: 80,
compressionLevel: isPng ? 9 : undefined
})
.toBuffer();
const settings = await db.query.setting.findFirst({ const settings = await db.query.setting.findFirst({
where: eq(drizzleDb.schemas.setting.name, "system"), where: eq(drizzleDb.schemas.setting.name, "system"),
with: { with: {
@@ -45,7 +59,7 @@ export const uploadUserImageAction = userAction.schema(
action: "upload", action: "upload",
data: { data: {
path: path, path: path,
file: buffer, file: compressedBuffer,
url: true url: true
}, },
metadata: { metadata: {