mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: refactoring, adding express api.
This commit is contained in:
@@ -26,20 +26,6 @@ export async function GET(
|
||||
return NextResponse.json({error: "Missing storageId in search params"}, {status: 404})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// const settings = await db.query.setting.findFirst({
|
||||
// where: eq(drizzleDb.schemas.setting.name, "system"),
|
||||
// with: {
|
||||
// storageChannel: true
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// if (!settings || !settings.storageChannel) {
|
||||
// return NextResponse.json({error: "Unable to get settings or no default storage channel"});
|
||||
// }
|
||||
|
||||
|
||||
const ext = fileName.split(".").pop()?.toLowerCase();
|
||||
const contentType =
|
||||
ext === "png"
|
||||
@@ -69,7 +55,7 @@ export async function GET(
|
||||
|
||||
const result = await dispatchStorage(input, undefined, storageId);
|
||||
|
||||
if (!result.file || !Buffer.isBuffer(result.file)) {
|
||||
if (!result.file || !(result.file instanceof Readable)) {
|
||||
console.error(`An error occurred while getting file :`, result);
|
||||
return NextResponse.json(
|
||||
{error: "Invalid file payload"},
|
||||
@@ -77,7 +63,7 @@ export async function GET(
|
||||
);
|
||||
}
|
||||
|
||||
const fileStream = Readable.from(result.file);
|
||||
const fileStream = Readable.from(result.file as Readable);
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
|
||||
@@ -3,11 +3,11 @@ name: portabase-prod
|
||||
services:
|
||||
|
||||
app:
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: docker/dockerfile/Dockerfile
|
||||
# target: prod
|
||||
image: solucetechnologies/portabase:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/dockerfile/Dockerfile
|
||||
target: prod
|
||||
# image: solucetechnologies/portabase:latest
|
||||
ports:
|
||||
- '8887:80'
|
||||
environment:
|
||||
|
||||
@@ -62,7 +62,6 @@ RUN pnpm run build
|
||||
|
||||
|
||||
|
||||
|
||||
FROM base AS prod
|
||||
|
||||
WORKDIR /app
|
||||
@@ -70,6 +69,9 @@ WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
@@ -81,14 +83,18 @@ RUN mkdir -p /app/private/uploads
|
||||
RUN chown -R nextjs:nodejs /app/private
|
||||
RUN chown -R nextjs:nodejs /app/public
|
||||
|
||||
|
||||
COPY --from=builder /app/next.config.ts ./
|
||||
COPY --from=builder /app/server ./server
|
||||
COPY --from=builder /app/src ./src
|
||||
|
||||
COPY --from=builder /app/portabase.config.ts ./
|
||||
COPY --from=builder /app/drizzle.config.ts ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --chown=nextjs:nodejs src/db ./src/db
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
|
||||
USER root
|
||||
|
||||
COPY ./docker/entrypoints/app-prod-entrypoint.sh /app/app-prod-entrypoint.sh
|
||||
|
||||
@@ -7,7 +7,7 @@ else
|
||||
echo "[WARN] No TZ provided, using default container timezone"
|
||||
fi
|
||||
|
||||
node server/server.js
|
||||
pnpm run start
|
||||
|
||||
exec "$@"
|
||||
|
||||
|
||||
+5
-6
@@ -4,10 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development tsx watch server/server.ts",
|
||||
"build": "next build",
|
||||
"start": "NODE_ENV=production node server/server.js",
|
||||
|
||||
|
||||
"build": "next build --experimental-build-mode compile",
|
||||
"start": "NODE_ENV=production tsx server/server.ts",
|
||||
"lint": "next lint",
|
||||
"email": "email dev --dir ./src/components/emails",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
@@ -71,7 +69,7 @@
|
||||
"lucide-react": "^0.553.0",
|
||||
"minio": "^8.0.5",
|
||||
"motion": "^12.23.24",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.5",
|
||||
"next-safe-action": "^7.10.8",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-cron": "^4.2.1",
|
||||
@@ -115,8 +113,9 @@
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@zenstackhq/openapi": "^2.14.2",
|
||||
"@zenstackhq/tanstack-query": "^2.14.2",
|
||||
"baseline-browser-mapping": "^2.8.32",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.0",
|
||||
"eslint-config-next": "^16.0.1",
|
||||
"eslint-plugin-tailwindcss": "^3.18.0",
|
||||
|
||||
Generated
+62
-58
@@ -127,7 +127,7 @@ importers:
|
||||
version: 6.0.0
|
||||
better-auth:
|
||||
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.1.5(@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)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -174,11 +174,11 @@ importers:
|
||||
specifier: ^12.23.24
|
||||
version: 12.24.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next:
|
||||
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)
|
||||
specifier: 16.1.5
|
||||
version: 16.1.5(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next-safe-action:
|
||||
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.1.5(@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-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -301,11 +301,14 @@ importers:
|
||||
specifier: ^2.14.2
|
||||
version: 2.22.2(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(typescript@5.9.3)(zod@3.25.76)
|
||||
baseline-browser-mapping:
|
||||
specifier: ^2.8.32
|
||||
version: 2.9.11
|
||||
specifier: ^2.9.19
|
||||
version: 2.9.19
|
||||
drizzle-kit:
|
||||
specifier: ^0.31.1
|
||||
version: 0.31.8
|
||||
esbuild:
|
||||
specifier: ^0.27.2
|
||||
version: 0.27.2
|
||||
eslint:
|
||||
specifier: ^9.39.0
|
||||
version: 9.39.2(jiti@2.6.1)
|
||||
@@ -1610,8 +1613,8 @@ packages:
|
||||
'@next/env@15.5.2':
|
||||
resolution: {integrity: sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==}
|
||||
|
||||
'@next/env@16.0.10':
|
||||
resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==}
|
||||
'@next/env@16.1.5':
|
||||
resolution: {integrity: sha512-CRSCPJiSZoi4Pn69RYBDI9R7YK2g59vLexPQFXY0eyw+ILevIenCywzg+DqmlBik9zszEnw2HLFOUlLAcJbL7g==}
|
||||
|
||||
'@next/eslint-plugin-next@16.1.1':
|
||||
resolution: {integrity: sha512-Ovb/6TuLKbE1UiPcg0p39Ke3puyTCIKN9hGbNItmpQsp+WX3qrjO3WaMVSi6JHr9X1NrmthqIguVHodMJbh/dw==}
|
||||
@@ -1622,8 +1625,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-arm64@16.0.10':
|
||||
resolution: {integrity: sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==}
|
||||
'@next/swc-darwin-arm64@16.1.5':
|
||||
resolution: {integrity: sha512-eK7Wdm3Hjy/SCL7TevlH0C9chrpeOYWx2iR7guJDaz4zEQKWcS1IMVfMb9UKBFMg1XgzcPTYPIp1Vcpukkjg6Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
@@ -1634,8 +1637,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@16.0.10':
|
||||
resolution: {integrity: sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==}
|
||||
'@next/swc-darwin-x64@16.1.5':
|
||||
resolution: {integrity: sha512-foQscSHD1dCuxBmGkbIr6ScAUF6pRoDZP6czajyvmXPAOFNnQUJu2Os1SGELODjKp/ULa4fulnBWoHV3XdPLfA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
@@ -1646,8 +1649,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.0.10':
|
||||
resolution: {integrity: sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==}
|
||||
'@next/swc-linux-arm64-gnu@16.1.5':
|
||||
resolution: {integrity: sha512-qNIb42o3C02ccIeSeKjacF3HXotGsxh/FMk/rSRmCzOVMtoWH88odn2uZqF8RLsSUWHcAqTgYmPD3pZ03L9ZAA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -1658,8 +1661,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.0.10':
|
||||
resolution: {integrity: sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==}
|
||||
'@next/swc-linux-arm64-musl@16.1.5':
|
||||
resolution: {integrity: sha512-U+kBxGUY1xMAzDTXmuVMfhaWUZQAwzRaHJ/I6ihtR5SbTVUEaDRiEU9YMjy1obBWpdOBuk1bcm+tsmifYSygfw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -1670,8 +1673,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.0.10':
|
||||
resolution: {integrity: sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==}
|
||||
'@next/swc-linux-x64-gnu@16.1.5':
|
||||
resolution: {integrity: sha512-gq2UtoCpN7Ke/7tKaU7i/1L7eFLfhMbXjNghSv0MVGF1dmuoaPeEVDvkDuO/9LVa44h5gqpWeJ4mRRznjDv7LA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -1682,8 +1685,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.0.10':
|
||||
resolution: {integrity: sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==}
|
||||
'@next/swc-linux-x64-musl@16.1.5':
|
||||
resolution: {integrity: sha512-bQWSE729PbXT6mMklWLf8dotislPle2L70E9q6iwETYEOt092GDn0c+TTNj26AjmeceSsC4ndyGsK5nKqHYXjQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -1694,8 +1697,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.0.10':
|
||||
resolution: {integrity: sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==}
|
||||
'@next/swc-win32-arm64-msvc@16.1.5':
|
||||
resolution: {integrity: sha512-LZli0anutkIllMtTAWZlDqdfvjWX/ch8AFK5WgkNTvaqwlouiD1oHM+WW8RXMiL0+vAkAJyAGEzPPjO+hnrSNQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
@@ -1706,8 +1709,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.0.10':
|
||||
resolution: {integrity: sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==}
|
||||
'@next/swc-win32-x64-msvc@16.1.5':
|
||||
resolution: {integrity: sha512-7is37HJTNQGhjPpQbkKjKEboHYQnCgpVt/4rBrrln0D9nderNxZ8ZWs8w1fAtzUx7wEyYjQ+/13myFgFj6K2Ng==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -3627,8 +3630,8 @@ packages:
|
||||
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
|
||||
engines: {node: ^4.5.0 || >= 5.9}
|
||||
|
||||
baseline-browser-mapping@2.9.11:
|
||||
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
|
||||
baseline-browser-mapping@2.9.19:
|
||||
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
|
||||
hasBin: true
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
@@ -5539,8 +5542,8 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
next@16.0.10:
|
||||
resolution: {integrity: sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==}
|
||||
next@16.1.5:
|
||||
resolution: {integrity: sha512-f+wE+NSbiQgh3DSAlTaw2FwY5yGdVViAtp8TotNQj4kk4Q8Bh1sC/aL9aH+Rg1YAVn18OYXsRDT7U/079jgP7w==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -8240,7 +8243,7 @@ snapshots:
|
||||
|
||||
'@next/env@15.5.2': {}
|
||||
|
||||
'@next/env@16.0.10': {}
|
||||
'@next/env@16.1.5': {}
|
||||
|
||||
'@next/eslint-plugin-next@16.1.1':
|
||||
dependencies:
|
||||
@@ -8249,49 +8252,49 @@ snapshots:
|
||||
'@next/swc-darwin-arm64@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-arm64@16.0.10':
|
||||
'@next/swc-darwin-arm64@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.0.10':
|
||||
'@next/swc-darwin-x64@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.0.10':
|
||||
'@next/swc-linux-arm64-gnu@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.0.10':
|
||||
'@next/swc-linux-arm64-musl@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.0.10':
|
||||
'@next/swc-linux-x64-gnu@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.0.10':
|
||||
'@next/swc-linux-x64-musl@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.0.10':
|
||||
'@next/swc-win32-arm64-msvc@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.5.2':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.0.10':
|
||||
'@next/swc-win32-x64-msvc@16.1.5':
|
||||
optional: true
|
||||
|
||||
'@noble/ciphers@2.1.1': {}
|
||||
@@ -10345,7 +10348,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 22.19.3
|
||||
tapable: 2.3.0
|
||||
webpack: 5.104.1(esbuild@0.27.2)
|
||||
webpack: 5.104.1(esbuild@0.25.10)
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- esbuild
|
||||
@@ -10934,7 +10937,7 @@ snapshots:
|
||||
|
||||
base64id@2.0.0: {}
|
||||
|
||||
baseline-browser-mapping@2.9.11: {}
|
||||
baseline-browser-mapping@2.9.19: {}
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
dependencies:
|
||||
@@ -10947,7 +10950,7 @@ snapshots:
|
||||
|
||||
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.1.5(@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):
|
||||
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/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))
|
||||
@@ -10963,7 +10966,7 @@ snapshots:
|
||||
nanostores: 1.1.0
|
||||
zod: 4.3.5
|
||||
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.1.5(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
@@ -11025,7 +11028,7 @@ snapshots:
|
||||
|
||||
browserslist@4.28.1:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.9.11
|
||||
baseline-browser-mapping: 2.9.19
|
||||
caniuse-lite: 1.0.30001762
|
||||
electron-to-chromium: 1.5.267
|
||||
node-releases: 2.0.27
|
||||
@@ -12979,9 +12982,9 @@ snapshots:
|
||||
|
||||
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.1.5(@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):
|
||||
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.1.5(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
@@ -13015,24 +13018,25 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- 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.1.5(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
'@next/env': 16.0.10
|
||||
'@next/env': 16.1.5
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.9.19
|
||||
caniuse-lite: 1.0.30001762
|
||||
postcss: 8.4.31
|
||||
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)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.0.10
|
||||
'@next/swc-darwin-x64': 16.0.10
|
||||
'@next/swc-linux-arm64-gnu': 16.0.10
|
||||
'@next/swc-linux-arm64-musl': 16.0.10
|
||||
'@next/swc-linux-x64-gnu': 16.0.10
|
||||
'@next/swc-linux-x64-musl': 16.0.10
|
||||
'@next/swc-win32-arm64-msvc': 16.0.10
|
||||
'@next/swc-win32-x64-msvc': 16.0.10
|
||||
'@next/swc-darwin-arm64': 16.1.5
|
||||
'@next/swc-darwin-x64': 16.1.5
|
||||
'@next/swc-linux-arm64-gnu': 16.1.5
|
||||
'@next/swc-linux-arm64-musl': 16.1.5
|
||||
'@next/swc-linux-x64-gnu': 16.1.5
|
||||
'@next/swc-linux-x64-musl': 16.1.5
|
||||
'@next/swc-win32-arm64-msvc': 16.1.5
|
||||
'@next/swc-win32-x64-msvc': 16.1.5
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -14257,14 +14261,14 @@ snapshots:
|
||||
ansi-escapes: 4.3.2
|
||||
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:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 4.3.3
|
||||
serialize-javascript: 6.0.2
|
||||
terser: 5.44.1
|
||||
webpack: 5.104.1(esbuild@0.27.2)
|
||||
webpack: 5.104.1(esbuild@0.25.10)
|
||||
optionalDependencies:
|
||||
esbuild: 0.25.10
|
||||
|
||||
@@ -14629,7 +14633,7 @@ snapshots:
|
||||
|
||||
webpack-sources@3.3.3: {}
|
||||
|
||||
webpack@5.104.1(esbuild@0.27.2):
|
||||
webpack@5.104.1(esbuild@0.25.10):
|
||||
dependencies:
|
||||
'@types/eslint-scope': 3.7.7
|
||||
'@types/estree': 1.0.8
|
||||
@@ -14653,7 +14657,7 @@ snapshots:
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 4.3.3
|
||||
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
|
||||
webpack-sources: 3.3.3
|
||||
transitivePeerDependencies:
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import next from "next";
|
||||
import express from "express";
|
||||
|
||||
import router from "./src/features/api/router";
|
||||
|
||||
const port = Number(process.env.PORT) || 80;
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
const app = next({ dev, port: port, turbopack: true });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
app.prepare().then(() => {
|
||||
const server = express();
|
||||
|
||||
server.use("/services/v1", router);
|
||||
|
||||
|
||||
server.use((req, res, next) => {
|
||||
if (req.path.startsWith('/services/v1')) return next();
|
||||
handle(req, res).catch((err) => {
|
||||
console.error('Next.js App Router error:', err);
|
||||
res.status(500).send('Internal Server Error');
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`Next.js App Router running at http://localhost:${port}`);
|
||||
console.log(`Express API running at http://localhost:${port}/express`);
|
||||
});
|
||||
});
|
||||
+4
-3
@@ -8,8 +8,9 @@ const dev = process.env.NODE_ENV !== "production";
|
||||
async function start() {
|
||||
const nextApp = next({
|
||||
dev,
|
||||
port,
|
||||
turbopack: true
|
||||
hostname: "0.0.0.0",
|
||||
turbopack: dev,
|
||||
port
|
||||
});
|
||||
|
||||
const handle = nextApp.getRequestHandler();
|
||||
@@ -28,7 +29,7 @@ async function start() {
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, () => {
|
||||
server.listen(port, "0.0.0.0", () => {
|
||||
console.log(
|
||||
`NEXT APP → http://localhost:${port}`
|
||||
);
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
import {Backup, DatabaseWith} from "@/db/schema/07_database";
|
||||
import {dispatchStorage} from "@/features/storages/dispatch";
|
||||
import type {StorageInput, StorageResult} from "@/features/storages/types";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
import {eq} from "drizzle-orm";
|
||||
import {db} from "@/db";
|
||||
import {PassThrough} from "stream";
|
||||
import fs from "node:fs";
|
||||
import forge from "node-forge";
|
||||
import crypto from "node:crypto";
|
||||
import {pipeline} from "node:stream";
|
||||
import { promisify } from "util";
|
||||
|
||||
|
||||
export async function storeBackupFilesStream(
|
||||
backup: Backup,
|
||||
database: DatabaseWith,
|
||||
fileStream: NodeJS.ReadableStream,
|
||||
fileName: string
|
||||
): Promise<StorageResult[]> {
|
||||
|
||||
const settings = await db.query.setting.findFirst({
|
||||
where: eq(drizzleDb.schemas.setting.name, "system"),
|
||||
with: {storageChannel: true},
|
||||
});
|
||||
|
||||
const defaultPolicy = settings?.storageChannel
|
||||
? [{
|
||||
id: null,
|
||||
storageChannelId: settings.storageChannel.id,
|
||||
enabled: settings.storageChannel.enabled,
|
||||
}]
|
||||
: [];
|
||||
|
||||
const enabledPolicies = database.storagePolicies?.filter(p => p.enabled) ?? [];
|
||||
const policies = enabledPolicies.length ? enabledPolicies : defaultPolicy;
|
||||
|
||||
if (!policies.length) {
|
||||
await db.update(drizzleDb.schemas.backup)
|
||||
.set(withUpdatedAt({status: "failed"}))
|
||||
.where(eq(drizzleDb.schemas.backup.id, backup.id));
|
||||
return [];
|
||||
}
|
||||
|
||||
const storagePath = `backups/${database.project?.slug}/${fileName}`;
|
||||
// const teeStreams = teeStreamSafe(fileStream, policies.length);
|
||||
const teeStreams = teeStream(fileStream, policies.length);
|
||||
|
||||
|
||||
const results = await Promise.all(
|
||||
policies.map(async (policy, index) => {
|
||||
const pass = teeStreams[index];
|
||||
console.log("policy", index, policy.id)
|
||||
const [backupStorage] = await db
|
||||
.insert(drizzleDb.schemas.backupStorage)
|
||||
.values({
|
||||
backupId: backup.id,
|
||||
storageChannelId: policy.storageChannelId,
|
||||
status: "pending",
|
||||
path: storagePath,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const input: StorageInput = {
|
||||
action: "upload",
|
||||
data: {
|
||||
path: storagePath,
|
||||
file: teeStreams[index],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
let result: StorageResult;
|
||||
|
||||
try {
|
||||
result = policy.id
|
||||
? await dispatchStorage(input, policy.id)
|
||||
: await dispatchStorage(input, undefined, policy.storageChannelId);
|
||||
} catch (err: any) {
|
||||
pass.destroy(err);
|
||||
result = {success: false, provider: null, error: err.message};
|
||||
}
|
||||
|
||||
await db.update(drizzleDb.schemas.backupStorage)
|
||||
.set(withUpdatedAt({status: result.success ? "success" : "failed"}))
|
||||
.where(eq(drizzleDb.schemas.backupStorage.id, backupStorage.id));
|
||||
|
||||
return result;
|
||||
})
|
||||
);
|
||||
console.log("result upload", results);
|
||||
|
||||
const backupStatus = results.some(r => r.success) ? "success" : "failed";
|
||||
|
||||
await db.update(drizzleDb.schemas.backup)
|
||||
.set(withUpdatedAt({status: backupStatus}))
|
||||
.where(eq(drizzleDb.schemas.backup.id, backup.id));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
export function createDecryptionStream(
|
||||
encryptedAesKeyHex: string,
|
||||
ivHex: string
|
||||
) {
|
||||
const privateKeyPem = fs.readFileSync(
|
||||
"private/keys/server_private.pem",
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
|
||||
|
||||
const encryptedBytes = forge.util.hexToBytes(encryptedAesKeyHex);
|
||||
const aesKeyBytes = privateKey.decrypt(encryptedBytes, "RSA-OAEP", {
|
||||
md: forge.md.sha256.create(),
|
||||
mgf1: {md: forge.md.sha256.create()},
|
||||
});
|
||||
|
||||
const aesKey = Buffer.from(aesKeyBytes, "binary");
|
||||
const iv = Buffer.from(ivHex, "hex");
|
||||
|
||||
return crypto.createDecipheriv("aes-256-cbc", aesKey, iv);
|
||||
}
|
||||
|
||||
|
||||
export function getFileExtension(dbType: string) {
|
||||
switch (dbType) {
|
||||
case "postgresql":
|
||||
return ".dump";
|
||||
case "mysql":
|
||||
return ".sql";
|
||||
default:
|
||||
return ".dump";
|
||||
}
|
||||
}
|
||||
|
||||
const pipelinePromise = promisify(pipeline);
|
||||
|
||||
function teeStream(stream: NodeJS.ReadableStream, n: number): PassThrough[] {
|
||||
const taps = Array.from({ length: n }, () => new PassThrough());
|
||||
taps.forEach(tap => pipelinePromise(stream, tap).catch(err => tap.destroy(err)));
|
||||
return taps;
|
||||
}
|
||||
@@ -6,8 +6,10 @@ import {db} from "@/db";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {eq} from "drizzle-orm";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
import {saveStreamToTempFile} from "@/features/api/upload/helpers/file";
|
||||
|
||||
import {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
|
||||
import {eventEmitter} from "@/features/shared/event";
|
||||
import forge from "node-forge";
|
||||
import crypto from "node:crypto";
|
||||
|
||||
/**
|
||||
* Save stream to a temporary file and upload to all storage providers in parallel.
|
||||
@@ -15,12 +17,11 @@ import {saveStreamToTempFile} from "@/features/api/upload/helpers/file";
|
||||
export default async function uploadTempFileToProviders(
|
||||
backup: Backup,
|
||||
database: DatabaseWith,
|
||||
inputStream: NodeJS.ReadableStream,
|
||||
// inputStream: NodeJS.ReadableStream,
|
||||
tmpPath: string,
|
||||
fileName: string
|
||||
): Promise<StorageResult[]> {
|
||||
|
||||
const tmpPath = await saveStreamToTempFile(inputStream, fileName);
|
||||
|
||||
const stats = fs.statSync(tmpPath);
|
||||
const fileSize = stats.size;
|
||||
console.log(tmpPath);
|
||||
@@ -78,8 +79,6 @@ export default async function uploadTempFileToProviders(
|
||||
} catch (err: any) {
|
||||
result = {success: false, provider: null, error: err.message};
|
||||
}
|
||||
console.log(result);
|
||||
|
||||
await db.update(drizzleDb.schemas.backupStorage)
|
||||
.set(withUpdatedAt({status: result.success ? "success" : "failed"}))
|
||||
.where(eq(drizzleDb.schemas.backupStorage.id, backupStorage.id));
|
||||
@@ -99,6 +98,36 @@ export default async function uploadTempFileToProviders(
|
||||
fs.existsSync(tmpPath) && fs.unlinkSync(tmpPath);
|
||||
|
||||
console.log(results);
|
||||
eventEmitter.emit('modification', {update: true});
|
||||
|
||||
if (!backupStatus) {
|
||||
await sendNotificationsBackupRestore(database, "error_backup");
|
||||
}
|
||||
await sendNotificationsBackupRestore(database, "success_backup");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
export function createDecryptionStream(
|
||||
encryptedAesKeyHex: string,
|
||||
ivHex: string
|
||||
) {
|
||||
const privateKeyPem = fs.readFileSync(
|
||||
"private/keys/server_private.pem",
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
|
||||
|
||||
const encryptedBytes = forge.util.hexToBytes(encryptedAesKeyHex);
|
||||
const aesKeyBytes = privateKey.decrypt(encryptedBytes, "RSA-OAEP", {
|
||||
md: forge.md.sha256.create(),
|
||||
mgf1: {md: forge.md.sha256.create()},
|
||||
});
|
||||
|
||||
const aesKey = Buffer.from(aesKeyBytes, "binary");
|
||||
const iv = Buffer.from(ivHex, "hex");
|
||||
|
||||
return crypto.createDecipheriv("aes-256-cbc", aesKey, iv);
|
||||
}
|
||||
@@ -13,3 +13,15 @@ export async function saveStreamToTempFile(stream: NodeJS.ReadableStream, fileNa
|
||||
await pipelineAsync(stream, writeStream);
|
||||
return tmpPath;
|
||||
}
|
||||
|
||||
|
||||
export function getFileExtension(dbType: string) {
|
||||
switch (dbType) {
|
||||
case "postgresql":
|
||||
return ".dump";
|
||||
case "mysql":
|
||||
return ".sql";
|
||||
default:
|
||||
return ".dump";
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@ import {v4 as uuidv4} from "uuid";
|
||||
import {and, eq} from "drizzle-orm";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {db} from "@/db";
|
||||
import {createDecryptionStream, getFileExtension} from "./helpers";
|
||||
import {isUuidv4} from "@/utils/verify-uuid";
|
||||
import uploadTempFileToProviders from "@/features/api/upload/helpers/common";
|
||||
import uploadTempFileToProviders, {createDecryptionStream} from "@/features/api/upload/helpers/common";
|
||||
import {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
|
||||
import {Backup} from "@/db/schema/07_database";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
import {eventEmitter} from "@/features/shared/event";
|
||||
import {getFileExtension, saveStreamToTempFile} from "@/features/api/upload/helpers/file";
|
||||
|
||||
const router: Router = express.Router();
|
||||
|
||||
@@ -86,9 +86,15 @@ router.post("/:agentId", async (req: Request, res: Response) => {
|
||||
|
||||
const decryptedStream = req.pipe(decipher);
|
||||
|
||||
await uploadTempFileToProviders(backup, database, decryptedStream, fileName);
|
||||
await sendNotificationsBackupRestore(database, "success_backup");
|
||||
eventEmitter.emit('modification', {update: true});
|
||||
const tmpPath = await saveStreamToTempFile(decryptedStream, fileName);
|
||||
|
||||
if (!tmpPath) {
|
||||
return res.status(500).json({
|
||||
error: "Unable to save tmp backup file",
|
||||
});
|
||||
}
|
||||
|
||||
uploadTempFileToProviders(backup, database, tmpPath, fileName);
|
||||
|
||||
return res.json({success: true});
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as drizzleDb from "@/db";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
import {eq} from "drizzle-orm";
|
||||
import {db} from "@/db";
|
||||
import crypto, {createHash} from "crypto";
|
||||
import {createHash} from "crypto";
|
||||
import {getServerUrl} from "@/utils/get-server-url";
|
||||
import path from "path";
|
||||
|
||||
|
||||
@@ -1,36 +1,3 @@
|
||||
// import {drive_v3, google} from "googleapis";
|
||||
// import Drive = drive_v3.Drive;
|
||||
// import {GoogleDriveConfig} from "@/features/storages/providers/google-drive/types";
|
||||
//
|
||||
//
|
||||
// export async function getGoogleDriveClient(config: GoogleDriveConfig): Promise<Drive> {
|
||||
// const auth = new google.auth.JWT({
|
||||
// email: config.clientEmail,
|
||||
// key: config.privateKey?.replace(/\\n/g, "\n"),
|
||||
// scopes: ["https://www.googleapis.com/auth/drive"],
|
||||
// });
|
||||
//
|
||||
// return google.drive({
|
||||
// version: "v3",
|
||||
// auth,
|
||||
// });
|
||||
// }
|
||||
//
|
||||
//
|
||||
// export async function findFileByName(
|
||||
// drive: any,
|
||||
// name: string,
|
||||
// folderId: string
|
||||
// ): Promise<string | null> {
|
||||
// const res = await drive.files.list({
|
||||
// q: `name='${name}' and '${folderId}' in parents and trashed=false`,
|
||||
// fields: "files(id)",
|
||||
// pageSize: 1,
|
||||
// });
|
||||
//
|
||||
// return res.data.files?.[0]?.id ?? null;
|
||||
// }
|
||||
|
||||
import {drive_v3, google} from "googleapis";
|
||||
import {GoogleDriveConfig} from "@/features/storages/providers/google-drive/types";
|
||||
import Drive = drive_v3.Drive;
|
||||
@@ -42,7 +9,6 @@ export async function getGoogleDriveClient(config: GoogleDriveConfig): Promise<D
|
||||
const oauth2Client = new google.auth.OAuth2(
|
||||
config.clientId,
|
||||
config.clientSecret,
|
||||
// config.redirectUri
|
||||
baseUrl
|
||||
);
|
||||
|
||||
|
||||
@@ -77,8 +77,6 @@ export async function getGoogleDrive(
|
||||
const fileId = await resolveFilePath(client, input.data.path, config.folderId);
|
||||
if (!fileId) return {success: false, provider: "google-drive", error: "File not found"};
|
||||
|
||||
|
||||
|
||||
const res = await client.files.get(
|
||||
{fileId, alt: "media", supportsAllDrives: true},
|
||||
{responseType: "stream"}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
StorageProviderKind,
|
||||
StorageInput,
|
||||
StorageResult, StorageMetaData,
|
||||
StorageResult,
|
||||
} from '../types';
|
||||
|
||||
import {uploadLocal, getLocal, deleteLocal, pingLocal} from './local';
|
||||
@@ -39,8 +39,6 @@ const handlers: Record<StorageProviderKind, ProviderHandler> = {
|
||||
delete: deleteGoogleDrive,
|
||||
ping: pingGoogleDrive,
|
||||
}
|
||||
// gcs: null as any,
|
||||
// azure: null as any,
|
||||
};
|
||||
|
||||
export async function dispatchViaProvider(
|
||||
|
||||
@@ -35,26 +35,22 @@ export async function uploadS3(
|
||||
input: { data: StorageUploadInput, metadata?: StorageMetaData }
|
||||
): Promise<StorageResult> {
|
||||
const client = await getS3Client(config);
|
||||
console.log(client);
|
||||
await ensureBucket(config);
|
||||
|
||||
const key = `${BASE_DIR}${input.data.path}`;
|
||||
const file = input.data.file;
|
||||
|
||||
console.log(key)
|
||||
|
||||
let uploadStream: Readable;
|
||||
if (Buffer.isBuffer(file) || file instanceof Uint8Array) {
|
||||
uploadStream = Readable.from(file);
|
||||
} else if ((file as any).pipe) {
|
||||
uploadStream = file;
|
||||
} else {
|
||||
throw new Error("Unsupported file type for streaming upload");
|
||||
return {success: false, provider: "s3", error: "Unsupported file type for streaming upload"};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await client.putObject(config.bucketName, key, uploadStream, input.data.size);
|
||||
console.log(result);
|
||||
} catch (err: any) {
|
||||
return {success: false, provider: "s3", error: err.message};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user