mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: refactoring of storage method
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Generated
+20
-18
@@ -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();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user