fix: refactoring, adding express api.

This commit is contained in:
charlesgauthereau
2026-01-31 22:15:01 +01:00
parent 7c5d57ddc1
commit cc87cf68b5
33 changed files with 1028 additions and 142 deletions
+1 -8
View File
@@ -1,7 +1,6 @@
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {isUuidv4} from "@/utils/verify-uuid"; import {isUuidv4} from "@/utils/verify-uuid";
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
import {eventEmitter} from "../../../events/route";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {db} from "@/db"; import {db} from "@/db";
import {Backup} from "@/db/schema/07_database"; import {Backup} from "@/db/schema/07_database";
@@ -10,6 +9,7 @@ import {withUpdatedAt} from "@/db/utils";
import {decryptedDump, getFileExtension} from "./helpers"; import {decryptedDump, getFileExtension} from "./helpers";
import {sendNotificationsBackupRestore} from "@/features/notifications/helpers"; import {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
import {storeBackupFiles} from "@/features/storages/helpers"; import {storeBackupFiles} from "@/features/storages/helpers";
import {eventEmitter} from "@/features/shared/event";
export async function POST( export async function POST(
request: Request, request: Request,
@@ -35,8 +35,6 @@ export async function POST(
const method = formData.get("method") as string | null; const method = formData.get("method") as string | null;
if (!generatedId || !isUuidv4(generatedId)) { if (!generatedId || !isUuidv4(generatedId)) {
return NextResponse.json( return NextResponse.json(
{error: "generatedId is not a valid UUID"}, {error: "generatedId is not a valid UUID"},
@@ -116,7 +114,6 @@ export async function POST(
} }
if (!file) { if (!file) {
return NextResponse.json( return NextResponse.json(
{error: "File is required for successful backup"}, {error: "File is required for successful backup"},
@@ -132,9 +129,6 @@ export async function POST(
const buffer = Buffer.from(await decryptedFile.arrayBuffer()); const buffer = Buffer.from(await decryptedFile.arrayBuffer());
console.log(extension)
const storageResults = await storeBackupFiles(backup, database, buffer, fileName) const storageResults = await storeBackupFiles(backup, database, buffer, fileName)
eventEmitter.emit('modification', {update: true}); eventEmitter.emit('modification', {update: true});
@@ -173,4 +167,3 @@ export async function POST(
); );
} }
} }
+1 -1
View File
@@ -1,10 +1,10 @@
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {isUuidv4} from "@/utils/verify-uuid"; import {isUuidv4} from "@/utils/verify-uuid";
import {eventEmitter} from "../../../events/route";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {db} from "@/db"; import {db} from "@/db";
import {and, eq} from "drizzle-orm"; import {and, eq} from "drizzle-orm";
import {sendNotificationsBackupRestore} from "@/features/notifications/helpers"; import {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
import {eventEmitter} from "@/features/shared/event";
export type BodyResultRestore = { export type BodyResultRestore = {
generatedId: string generatedId: string
+1 -1
View File
@@ -1,12 +1,12 @@
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {handleDatabases} from "./helpers"; import {handleDatabases} from "./helpers";
import {eventEmitter} from "../../../events/route";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {db} from "@/db"; import {db} from "@/db";
import {EDbmsSchema} from "@/db/schema/types"; import {EDbmsSchema} from "@/db/schema/types";
import {eq} from "drizzle-orm"; import {eq} from "drizzle-orm";
import {isUuidv4} from "@/utils/verify-uuid"; import {isUuidv4} from "@/utils/verify-uuid";
import {withUpdatedAt} from "@/db/utils"; import {withUpdatedAt} from "@/db/utils";
import {eventEmitter} from "@/features/shared/event";
export type databaseAgent = { export type databaseAgent = {
name: string, name: string,
+2 -1
View File
@@ -2,8 +2,9 @@ import {EventEmitter} from 'events';
import {auth} from "@/lib/auth/auth"; import {auth} from "@/lib/auth/auth";
import {headers} from "next/headers"; import {headers} from "next/headers";
import {NextResponse} from "next/server"; import {NextResponse} from "next/server";
import {eventEmitter} from "@/features/shared/event";
export const eventEmitter = new EventEmitter(); // export const eventEmitter = new EventEmitter();
export async function GET(request: Request) { export async function GET(request: Request) {
+3 -4
View File
@@ -35,10 +35,9 @@ export async function GET(
const result = await dispatchStorage(input, undefined, storageId); const result = await dispatchStorage(input, undefined, storageId);
if (!result.success) { if (!result.success) {
return NextResponse.json({error: "Enable to get file from privided storage channel, an error occurred !"}) return NextResponse.json({error: "Enable to get file from provided storage channel, an error occurred !"})
} }
const fileName = path.basename(pathFromUrl); const fileName = path.basename(pathFromUrl);
const crypto = require('crypto'); const crypto = require('crypto');
@@ -58,14 +57,14 @@ export async function GET(
); );
} }
if (!result.file || !Buffer.isBuffer(result.file)) { if (!result.file || !(result.file instanceof Readable)) {
return NextResponse.json( return NextResponse.json(
{error: "Invalid file payload"}, {error: "Invalid file payload"},
{status: 500} {status: 500}
); );
} }
const fileStream = Readable.from(result.file); const fileStream = Readable.from(result.file as Readable);
const stream = new ReadableStream({ const stream = new ReadableStream({
start(controller) { start(controller) {
+1 -2
View File
@@ -7,8 +7,7 @@ else
echo "[WARN] No TZ provided, using default container timezone" echo "[WARN] No TZ provided, using default container timezone"
fi fi
node server/server.js
node server.js
exec "$@" exec "$@"
+1
View File
@@ -41,6 +41,7 @@ const nextConfig: NextConfig = {
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,
}, },
experimental: { experimental: {
serverActions: { serverActions: {
bodySizeLimit: "10gb", bodySizeLimit: "10gb",
+8 -4
View File
@@ -1,11 +1,13 @@
{ {
"name": "portabase", "name": "portabase",
"version": "1.2.4-rc.2", "version": "1.2.4",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack -p 8887", "dev": "NODE_ENV=development tsx watch server/server.ts",
"build": "next build --experimental-build-mode compile", "build": "next build",
"start": "next start", "start": "NODE_ENV=production node server/server.js",
"lint": "next lint", "lint": "next lint",
"email": "email dev --dir ./src/components/emails", "email": "email dev --dir ./src/components/emails",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
@@ -47,6 +49,7 @@
"@t3-oss/env-nextjs": "^0.13.4", "@t3-oss/env-nextjs": "^0.13.4",
"@tanstack/react-query": "^5.76.1", "@tanstack/react-query": "^5.76.1",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@types/express": "^5.0.6",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@zenstackhq/runtime": "2.14.2", "@zenstackhq/runtime": "2.14.2",
@@ -62,6 +65,7 @@
"drizzle-orm": "^0.43.1", "drizzle-orm": "^0.43.1",
"drizzle-zod": "^0.7.1", "drizzle-zod": "^0.7.1",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"express": "^5.2.1",
"googleapis": "^170.1.0", "googleapis": "^170.1.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.553.0", "lucide-react": "^0.553.0",
+403 -16
View File
@@ -107,6 +107,9 @@ importers:
'@tanstack/react-table': '@tanstack/react-table':
specifier: ^8.21.3 specifier: ^8.21.3
version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@types/express':
specifier: ^5.0.6
version: 5.0.6
'@types/nodemailer': '@types/nodemailer':
specifier: ^6.4.17 specifier: ^6.4.17
version: 6.4.21 version: 6.4.21
@@ -124,7 +127,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.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) 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)
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@@ -152,6 +155,9 @@ importers:
embla-carousel-react: embla-carousel-react:
specifier: ^8.6.0 specifier: ^8.6.0
version: 8.6.0(react@19.2.3) version: 8.6.0(react@19.2.3)
express:
specifier: ^5.2.1
version: 5.2.1
googleapis: googleapis:
specifier: ^170.1.0 specifier: ^170.1.0
version: 170.1.0 version: 170.1.0
@@ -169,10 +175,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.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 16.0.10(@babel/core@7.28.5)(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.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) 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)
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)
@@ -3071,6 +3077,12 @@ packages:
'@tybys/wasm-util@0.10.1': '@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
'@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
'@types/cors@2.8.19': '@types/cors@2.8.19':
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
@@ -3113,6 +3125,15 @@ packages:
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/express-serve-static-core@5.1.1':
resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==}
'@types/express@5.0.6':
resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
'@types/http-errors@2.0.5':
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -3143,6 +3164,12 @@ packages:
'@types/prismjs@1.26.5': '@types/prismjs@1.26.5':
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
'@types/qs@6.14.0':
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
'@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
'@types/react-dom@19.0.4': '@types/react-dom@19.0.4':
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
peerDependencies: peerDependencies:
@@ -3159,6 +3186,12 @@ packages:
'@types/react@19.2.7': '@types/react@19.2.7':
resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
'@types/send@1.2.1':
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
'@types/serve-static@2.2.0':
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
'@types/webpack@5.28.5': '@types/webpack@5.28.5':
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==} resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
@@ -3420,6 +3453,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
acorn-import-phases@1.0.4: acorn-import-phases@1.0.4:
resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@@ -3657,6 +3694,10 @@ packages:
block-stream2@2.1.0: block-stream2@2.1.0:
resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==}
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
boolbase@1.0.0: boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -3701,6 +3742,10 @@ packages:
resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
c12@3.1.0: c12@3.1.0:
resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
peerDependencies: peerDependencies:
@@ -3856,9 +3901,21 @@ packages:
constant-case@3.0.4: constant-case@3.0.4:
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
content-disposition@1.0.1:
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
engines: {node: '>=18'}
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
cookie@0.7.2: cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -4028,6 +4085,10 @@ packages:
defu@6.1.4: defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
destr@2.0.5: destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
@@ -4188,6 +4249,9 @@ packages:
ecdsa-sig-formatter@1.0.11: ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
effect@3.18.4: effect@3.18.4:
resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==}
@@ -4223,6 +4287,10 @@ packages:
emphasize@4.2.0: emphasize@4.2.0:
resolution: {integrity: sha512-yGKvcFUHlBsUPwlxTlzKLR8+zhpbitkFOMCUxN8fTJng9bdH3WNzUGkhdaGdjndSUgqmMPBN7umfwnUdLz5Axg==} resolution: {integrity: sha512-yGKvcFUHlBsUPwlxTlzKLR8+zhpbitkFOMCUxN8fTJng9bdH3WNzUGkhdaGdjndSUgqmMPBN7umfwnUdLz5Axg==}
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
end-of-stream@1.4.5: end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
@@ -4309,6 +4377,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'} engines: {node: '>=6'}
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
escape-string-regexp@4.0.0: escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -4443,6 +4514,10 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
eventemitter3@4.0.7: eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
@@ -4453,6 +4528,10 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'} engines: {node: '>=0.8.x'}
express@5.2.1:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
exsolve@1.0.8: exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@@ -4533,6 +4612,10 @@ packages:
resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
finalhandler@2.1.1:
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
engines: {node: '>= 18.0.0'}
find-up@5.0.0: find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -4560,6 +4643,10 @@ packages:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'} engines: {node: '>=12.20.0'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
fraction.js@4.3.7: fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@@ -4591,6 +4678,10 @@ packages:
react-dom: react-dom:
optional: true optional: true
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fs-constants@1.0.0: fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@@ -4773,6 +4864,10 @@ packages:
htmlparser2@8.0.2: htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
https-proxy-agent@5.0.0: https-proxy-agent@5.0.0:
resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -4781,6 +4876,10 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
iconv-lite@0.7.2:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
ieee754@1.2.1: ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -4817,6 +4916,10 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'} engines: {node: '>=12'}
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
ipaddr.js@2.3.0: ipaddr.js@2.3.0:
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@@ -4912,6 +5015,9 @@ packages:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
is-regex@1.2.1: is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -5242,6 +5348,14 @@ packages:
peerDependencies: peerDependencies:
react: ^18.0 || ^19.0 react: ^18.0 || ^19.0
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
merge-descriptors@2.0.0:
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
engines: {node: '>=18'}
merge-stream@2.0.0: merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -5369,6 +5483,10 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
neo-async@2.6.2: neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -5557,6 +5675,10 @@ packages:
ohash@2.0.11: ohash@2.0.11:
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
once@1.4.0: once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -5608,6 +5730,10 @@ packages:
parseley@0.12.1: parseley@0.12.1:
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
pascal-case@3.1.2: pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
@@ -5636,6 +5762,9 @@ packages:
resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
engines: {node: 20 || >=22} engines: {node: 20 || >=22}
path-to-regexp@8.3.0:
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
pathe@2.0.3: pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
@@ -5817,6 +5946,10 @@ packages:
resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
pump@3.0.3: pump@3.0.3:
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
@@ -5844,6 +5977,14 @@ packages:
randombytes@2.1.0: randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
rc9@2.1.2: rc9@2.1.2:
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
@@ -6041,6 +6182,10 @@ packages:
rou3@0.7.12: rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
run-parallel@1.2.0: run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -6090,12 +6235,20 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
sentence-case@3.0.4: sentence-case@3.0.4:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
serialize-javascript@6.0.2: serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
set-cookie-parser@2.7.2: set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
@@ -6111,6 +6264,9 @@ packages:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
sharp@0.34.4: sharp@0.34.4:
resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==} resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -6226,6 +6382,10 @@ packages:
resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==}
engines: {node: '>=6'} engines: {node: '>=6'}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
stdin-discarder@0.2.2: stdin-discarder@0.2.2:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -6428,6 +6588,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
tr46@0.0.3: tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -6486,6 +6650,10 @@ packages:
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
engines: {node: '>=8'} engines: {node: '>=8'}
type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
typed-array-buffer@1.0.3: typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -6532,6 +6700,10 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
unrs-resolver@1.11.1: unrs-resolver@1.11.1:
resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
@@ -10035,6 +10207,15 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
optional: true optional: true
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
'@types/node': 22.19.3
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.19.3
'@types/cors@2.8.19': '@types/cors@2.8.19':
dependencies: dependencies:
'@types/node': 22.19.3 '@types/node': 22.19.3
@@ -10079,6 +10260,21 @@ snapshots:
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/express-serve-static-core@5.1.1':
dependencies:
'@types/node': 22.19.3
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 1.2.1
'@types/express@5.0.6':
dependencies:
'@types/body-parser': 1.19.6
'@types/express-serve-static-core': 5.1.1
'@types/serve-static': 2.2.0
'@types/http-errors@2.0.5': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/json5@0.0.29': {} '@types/json5@0.0.29': {}
@@ -10116,6 +10312,10 @@ snapshots:
'@types/prismjs@1.26.5': {} '@types/prismjs@1.26.5': {}
'@types/qs@6.14.0': {}
'@types/range-parser@1.2.7': {}
'@types/react-dom@19.0.4(@types/react@19.0.10)': '@types/react-dom@19.0.4(@types/react@19.0.10)':
dependencies: dependencies:
'@types/react': 19.0.10 '@types/react': 19.0.10
@@ -10132,11 +10332,20 @@ snapshots:
dependencies: dependencies:
csstype: 3.2.3 csstype: 3.2.3
'@types/send@1.2.1':
dependencies:
'@types/node': 22.19.3
'@types/serve-static@2.2.0':
dependencies:
'@types/http-errors': 2.0.5
'@types/node': 22.19.3
'@types/webpack@5.28.5(esbuild@0.25.10)': '@types/webpack@5.28.5(esbuild@0.25.10)':
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.25.10) webpack: 5.104.1(esbuild@0.27.2)
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/core' - '@swc/core'
- esbuild - esbuild
@@ -10537,6 +10746,11 @@ snapshots:
mime-types: 2.1.35 mime-types: 2.1.35
negotiator: 0.6.3 negotiator: 0.6.3
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
negotiator: 1.0.0
acorn-import-phases@1.0.4(acorn@8.15.0): acorn-import-phases@1.0.4(acorn@8.15.0):
dependencies: dependencies:
acorn: 8.15.0 acorn: 8.15.0
@@ -10733,7 +10947,7 @@ snapshots:
bcryptjs@2.4.3: {} bcryptjs@2.4.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): 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):
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))
@@ -10749,7 +10963,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.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: 16.0.10(@babel/core@7.28.5)(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)
@@ -10776,6 +10990,20 @@ snapshots:
dependencies: dependencies:
readable-stream: 3.6.2 readable-stream: 3.6.2
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 4.4.3
http-errors: 2.0.1
iconv-lite: 0.7.2
on-finished: 2.4.1
qs: 6.14.1
raw-body: 3.0.2
type-is: 2.0.1
transitivePeerDependencies:
- supports-color
boolbase@1.0.0: {} boolbase@1.0.0: {}
bowser@2.13.1: {} bowser@2.13.1: {}
@@ -10822,6 +11050,8 @@ snapshots:
buildcheck@0.0.7: buildcheck@0.0.7:
optional: true optional: true
bytes@3.1.2: {}
c12@3.1.0: c12@3.1.0:
dependencies: dependencies:
chokidar: 4.0.3 chokidar: 4.0.3
@@ -11001,8 +11231,14 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
upper-case: 2.0.2 upper-case: 2.0.2
content-disposition@1.0.1: {}
content-type@1.0.5: {}
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
cookie-signature@1.2.2: {}
cookie@0.7.2: {} cookie@0.7.2: {}
copy-anything@3.0.5: copy-anything@3.0.5:
@@ -11152,6 +11388,8 @@ snapshots:
defu@6.1.4: {} defu@6.1.4: {}
depd@2.0.0: {}
destr@2.0.5: {} destr@2.0.5: {}
detect-libc@2.1.2: {} detect-libc@2.1.2: {}
@@ -11251,6 +11489,8 @@ snapshots:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
ee-first@1.1.1: {}
effect@3.18.4: effect@3.18.4:
dependencies: dependencies:
'@standard-schema/spec': 1.1.0 '@standard-schema/spec': 1.1.0
@@ -11284,6 +11524,8 @@ snapshots:
highlight.js: 10.4.1 highlight.js: 10.4.1
lowlight: 1.17.0 lowlight: 1.17.0
encodeurl@2.0.0: {}
end-of-stream@1.4.5: end-of-stream@1.4.5:
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
@@ -11556,6 +11798,8 @@ snapshots:
escalade@3.2.0: {} escalade@3.2.0: {}
escape-html@1.0.3: {}
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
eslint-config-next@16.1.1(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): eslint-config-next@16.1.1(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
@@ -11774,12 +12018,47 @@ snapshots:
esutils@2.0.3: {} esutils@2.0.3: {}
etag@1.8.1: {}
eventemitter3@4.0.7: {} eventemitter3@4.0.7: {}
eventemitter3@5.0.1: {} eventemitter3@5.0.1: {}
events@3.3.0: {} events@3.3.0: {}
express@5.2.1:
dependencies:
accepts: 2.0.0
body-parser: 2.2.2
content-disposition: 1.0.1
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
debug: 4.4.3
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 2.1.1
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
mime-types: 3.0.2
on-finished: 2.4.1
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
qs: 6.14.1
range-parser: 1.2.1
router: 2.2.0
send: 1.2.1
serve-static: 2.2.1
statuses: 2.0.2
type-is: 2.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
exsolve@1.0.8: {} exsolve@1.0.8: {}
extend@3.0.2: {} extend@3.0.2: {}
@@ -11855,6 +12134,17 @@ snapshots:
filter-obj@1.1.0: {} filter-obj@1.1.0: {}
finalhandler@2.1.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
find-up@5.0.0: find-up@5.0.0:
dependencies: dependencies:
locate-path: 6.0.0 locate-path: 6.0.0
@@ -11882,6 +12172,8 @@ snapshots:
dependencies: dependencies:
fetch-blob: 3.2.0 fetch-blob: 3.2.0
forwarded@0.2.0: {}
fraction.js@4.3.7: {} fraction.js@4.3.7: {}
framer-motion@12.23.22(react-dom@19.0.0(react@19.0.0))(react@19.0.0): framer-motion@12.23.22(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
@@ -11902,6 +12194,8 @@ snapshots:
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)
fresh@2.0.0: {}
fs-constants@1.0.0: {} fs-constants@1.0.0: {}
fs-extra@11.3.0: fs-extra@11.3.0:
@@ -12123,6 +12417,14 @@ snapshots:
domutils: 3.2.2 domutils: 3.2.2
entities: 4.5.0 entities: 4.5.0
http-errors@2.0.1:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.2
toidentifier: 1.0.1
https-proxy-agent@5.0.0: https-proxy-agent@5.0.0:
dependencies: dependencies:
agent-base: 6.0.2 agent-base: 6.0.2
@@ -12137,6 +12439,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
iconv-lite@0.7.2:
dependencies:
safer-buffer: 2.1.2
ieee754@1.2.1: {} ieee754@1.2.1: {}
ignore@5.3.2: {} ignore@5.3.2: {}
@@ -12165,6 +12471,8 @@ snapshots:
internmap@2.0.3: {} internmap@2.0.3: {}
ipaddr.js@1.9.1: {}
ipaddr.js@2.3.0: {} ipaddr.js@2.3.0: {}
is-arguments@1.2.0: is-arguments@1.2.0:
@@ -12257,6 +12565,8 @@ snapshots:
is-plain-object@5.0.0: {} is-plain-object@5.0.0: {}
is-promise@4.0.0: {}
is-regex@1.2.1: is-regex@1.2.1:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -12552,6 +12862,10 @@ snapshots:
marked: 7.0.4 marked: 7.0.4
react: 19.2.3 react: 19.2.3
media-typer@1.1.0: {}
merge-descriptors@2.0.0: {}
merge-stream@2.0.0: {} merge-stream@2.0.0: {}
merge2@1.4.1: {} merge2@1.4.1: {}
@@ -12661,11 +12975,13 @@ snapshots:
negotiator@0.6.3: {} negotiator@0.6.3: {}
negotiator@1.0.0: {}
neo-async@2.6.2: {} neo-async@2.6.2: {}
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): 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):
dependencies: dependencies:
next: 16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: 16.0.10(@babel/core@7.28.5)(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:
@@ -12699,7 +13015,7 @@ snapshots:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
next@16.0.10(@babel/core@7.26.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): next@16.0.10(@babel/core@7.28.5)(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
@@ -12707,7 +13023,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.26.10)(react@19.2.3) styled-jsx: 5.1.6(@babel/core@7.28.5)(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
@@ -12830,6 +13146,10 @@ snapshots:
ohash@2.0.11: {} ohash@2.0.11: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
once@1.4.0: once@1.4.0:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
@@ -12907,6 +13227,8 @@ snapshots:
leac: 0.6.0 leac: 0.6.0
peberminta: 0.9.0 peberminta: 0.9.0
parseurl@1.3.3: {}
pascal-case@3.1.2: pascal-case@3.1.2:
dependencies: dependencies:
no-case: 3.0.4 no-case: 3.0.4
@@ -12935,6 +13257,8 @@ snapshots:
lru-cache: 11.2.4 lru-cache: 11.2.4
minipass: 7.1.2 minipass: 7.1.2
path-to-regexp@8.3.0: {}
pathe@2.0.3: {} pathe@2.0.3: {}
peberminta@0.9.0: {} peberminta@0.9.0: {}
@@ -13106,6 +13430,11 @@ snapshots:
'@types/node': 22.19.3 '@types/node': 22.19.3
long: 5.3.2 long: 5.3.2
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
pump@3.0.3: pump@3.0.3:
dependencies: dependencies:
end-of-stream: 1.4.5 end-of-stream: 1.4.5
@@ -13134,6 +13463,15 @@ snapshots:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
range-parser@1.2.1: {}
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
iconv-lite: 0.7.2
unpipe: 1.0.0
rc9@2.1.2: rc9@2.1.2:
dependencies: dependencies:
defu: 6.1.4 defu: 6.1.4
@@ -13391,6 +13729,16 @@ snapshots:
rou3@0.7.12: {} rou3@0.7.12: {}
router@2.2.0:
dependencies:
debug: 4.4.3
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
path-to-regexp: 8.3.0
transitivePeerDependencies:
- supports-color
run-parallel@1.2.0: run-parallel@1.2.0:
dependencies: dependencies:
queue-microtask: 1.2.3 queue-microtask: 1.2.3
@@ -13441,6 +13789,22 @@ snapshots:
semver@7.7.3: {} semver@7.7.3: {}
send@1.2.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 2.0.0
http-errors: 2.0.1
mime-types: 3.0.2
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
sentence-case@3.0.4: sentence-case@3.0.4:
dependencies: dependencies:
no-case: 3.0.4 no-case: 3.0.4
@@ -13451,6 +13815,15 @@ snapshots:
dependencies: dependencies:
randombytes: 2.1.0 randombytes: 2.1.0
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 1.2.1
transitivePeerDependencies:
- supports-color
set-cookie-parser@2.7.2: {} set-cookie-parser@2.7.2: {}
set-function-length@1.2.2: set-function-length@1.2.2:
@@ -13475,6 +13848,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
es-object-atoms: 1.1.1 es-object-atoms: 1.1.1
setprototypeof@1.2.0: {}
sharp@0.34.4: sharp@0.34.4:
dependencies: dependencies:
'@img/colour': 1.0.0 '@img/colour': 1.0.0
@@ -13675,6 +14050,8 @@ snapshots:
dependencies: dependencies:
type-fest: 0.7.1 type-fest: 0.7.1
statuses@2.0.2: {}
stdin-discarder@0.2.2: {} stdin-discarder@0.2.2: {}
stop-iteration-iterator@1.1.0: stop-iteration-iterator@1.1.0:
@@ -13787,12 +14164,12 @@ snapshots:
optionalDependencies: optionalDependencies:
'@babel/core': 7.26.10 '@babel/core': 7.26.10
styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.2.3): styled-jsx@5.1.6(@babel/core@7.28.5)(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.26.10 '@babel/core': 7.28.5
sucrase@3.35.1: sucrase@3.35.1:
dependencies: dependencies:
@@ -13880,14 +14257,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.27.2)): terser-webpack-plugin@5.3.16(esbuild@0.25.10)(webpack@5.104.1(esbuild@0.25.10)):
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.25.10) webpack: 5.104.1(esbuild@0.27.2)
optionalDependencies: optionalDependencies:
esbuild: 0.25.10 esbuild: 0.25.10
@@ -13925,6 +14302,8 @@ snapshots:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
toidentifier@1.0.1: {}
tr46@0.0.3: {} tr46@0.0.3: {}
traverse@0.6.11: traverse@0.6.11:
@@ -13985,6 +14364,12 @@ snapshots:
type-fest@0.7.1: {} type-fest@0.7.1: {}
type-is@2.0.1:
dependencies:
content-type: 1.0.5
media-typer: 1.1.0
mime-types: 3.0.2
typed-array-buffer@1.0.3: typed-array-buffer@1.0.3:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -14055,6 +14440,8 @@ snapshots:
universalify@2.0.1: {} universalify@2.0.1: {}
unpipe@1.0.0: {}
unrs-resolver@1.11.1: unrs-resolver@1.11.1:
dependencies: dependencies:
napi-postinstall: 0.3.4 napi-postinstall: 0.3.4
@@ -14242,7 +14629,7 @@ snapshots:
webpack-sources@3.3.3: {} webpack-sources@3.3.3: {}
webpack@5.104.1(esbuild@0.25.10): webpack@5.104.1(esbuild@0.27.2):
dependencies: dependencies:
'@types/eslint-scope': 3.7.7 '@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8 '@types/estree': 1.0.8
@@ -14266,7 +14653,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.27.2)) terser-webpack-plugin: 5.3.16(esbuild@0.25.10)(webpack@5.104.1(esbuild@0.25.10))
watchpack: 2.5.0 watchpack: 2.5.0
webpack-sources: 3.3.3 webpack-sources: 3.3.3
transitivePeerDependencies: transitivePeerDependencies:
+30
View File
@@ -0,0 +1,30 @@
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`);
});
});
+6
View File
@@ -0,0 +1,6 @@
import express from "express";
import router from "../src/features/api/router";
export function mountApi(app: express.Express) {
app.use("/services/v1", router);
}
+41
View File
@@ -0,0 +1,41 @@
import next from "next";
import express from "express";
import {mountApi} from "./api";
const port = Number(process.env.PORT) || 8887;
const dev = process.env.NODE_ENV !== "production";
async function start() {
const nextApp = next({
dev,
port,
turbopack: true
});
const handle = nextApp.getRequestHandler();
await nextApp.prepare();
const server = express();
mountApi(server);
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 APP → http://localhost:${port}`
);
console.log(
`API → http://localhost:${port}/services/v1`
);
});
}
start();
@@ -75,7 +75,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
if (inner?.success) { if (inner?.success) {
toast.success(inner.actionSuccess?.message); toast.success(inner.actionSuccess?.message);
if (action === "download") { if (action === "download") {
console.log(inner.value)
const url = inner.value const url = inner.value
if (typeof url === "string") { if (typeof url === "string") {
window.open(url, "_self"); window.open(url, "_self");
@@ -57,7 +57,7 @@ export const downloadBackupAction = userAction.schema(
} }
}; };
console.log(input)
const result = await dispatchStorage(input, undefined, backupStorage.storageChannelId); const result = await dispatchStorage(input, undefined, backupStorage.storageChannelId);
console.log(result); console.log(result);
@@ -106,8 +106,7 @@ export const ChannelPoliciesForm = ({
}); });
console.log(policiesToUpdate);
console.log(policiesToAdd);
const promises = kind === "notification" const promises = kind === "notification"
? [ ? [
+6
View File
@@ -0,0 +1,6 @@
import {Request, Response, NextFunction} from "express";
export function loggingMiddleware(req: Request, res: Response, next: NextFunction) {
console.log(`[API - SERVICES - V1] Received ${req.method} request : ${req.url} at ${new Date().toISOString()}`);
next();
}
+10
View File
@@ -0,0 +1,10 @@
import express, {Router} from "express";
import uploadRouter from "./upload";
import {loggingMiddleware} from "@/features/api/middleware";
const router: Router = express.Router();
router.use(loggingMiddleware);
router.use("/upload", uploadRouter);
export default router;
+145
View File
@@ -0,0 +1,145 @@
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;
}
+104
View File
@@ -0,0 +1,104 @@
import fs from "fs";
import {Backup, DatabaseWith} from "@/db/schema/07_database";
import {dispatchStorage} from "@/features/storages/dispatch";
import type {StorageInput, StorageResult} from "@/features/storages/types";
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";
/**
* Save stream to a temporary file and upload to all storage providers in parallel.
*/
export default async function uploadTempFileToProviders(
backup: Backup,
database: DatabaseWith,
inputStream: NodeJS.ReadableStream,
fileName: string
): Promise<StorageResult[]> {
const tmpPath = await saveStreamToTempFile(inputStream, fileName);
const stats = fs.statSync(tmpPath);
const fileSize = stats.size;
console.log(tmpPath);
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));
fs.existsSync(tmpPath) && fs.unlinkSync(tmpPath);
return [];
}
const storagePath = `backups/${database.project?.slug}/${fileName}`;
const results = await Promise.all(
policies.map(async (policy) => {
const fileStream = fs.createReadStream(tmpPath);
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: fileStream, size: fileSize},
};
let result: StorageResult;
try {
result = policy.id
? await dispatchStorage(input, policy.id)
: await dispatchStorage(input, undefined, policy.storageChannelId);
} 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));
return result;
})
);
const backupStatus = results.some(r => r.success) ? "success" : "failed";
await db.update(drizzleDb.schemas.backup)
.set(withUpdatedAt({
fileSize: fileSize,
status: backupStatus
}))
.where(eq(drizzleDb.schemas.backup.id, backup.id));
fs.existsSync(tmpPath) && fs.unlinkSync(tmpPath);
console.log(results);
return results;
}
+15
View File
@@ -0,0 +1,15 @@
import fs from "fs";
import path from "path";
import {promisify} from "util";
import {pipeline} from "stream";
const pipelineAsync = promisify(pipeline);
const TMP_DIR = path.join(process.cwd(), "private/uploads/tmp");
fs.mkdirSync(TMP_DIR, { recursive: true });
export async function saveStreamToTempFile(stream: NodeJS.ReadableStream, fileName: string): Promise<string> {
const tmpPath = path.join(TMP_DIR, fileName);
const writeStream = fs.createWriteStream(tmpPath);
await pipelineAsync(stream, writeStream);
return tmpPath;
}
+120
View File
@@ -0,0 +1,120 @@
import express, {Request, Response, Router} from "express";
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 {sendNotificationsBackupRestore} from "@/features/notifications/helpers";
import {Backup} from "@/db/schema/07_database";
import {withUpdatedAt} from "@/db/utils";
import {eventEmitter} from "@/features/shared/event";
const router: Router = express.Router();
router.post("/:agentId", async (req: Request, res: Response) => {
try {
const agentId = req.params.agentId as string | undefined;
const generatedId = req.headers["x-generated-id"] as string | undefined;
const status = req.headers["x-status"] as string | undefined;
const encryptedAesKeyHex = req.headers["x-aes-key"] as string | undefined;
const ivHex = req.headers["x-iv"] as string | undefined;
const method = (req.headers["x-method"] as string) ?? "manual";
const extension = req.headers["x-extension"] as string | undefined;
if (!generatedId || !encryptedAesKeyHex || !ivHex || !agentId || !status) {
return res.status(400).json({error: "Missing required headers/params"});
}
if (!isUuidv4(generatedId)) {
return res.status(400).json({error: "generatedId is not a valid UUID"});
}
const agent = await db.query.agent.findFirst({
where: eq(drizzleDb.schemas.agent.id, agentId),
});
if (!agent) {
return res.status(404).json({error: "Agent not found"});
}
const database = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.agentDatabaseId, generatedId),
with: {
project: true,
storagePolicies: true,
},
});
if (!database) {
return res.status(404).json({error: "Database not found"});
}
let backup: Backup | null | undefined = null;
if (method === "automatic") {
[backup] = await db
.insert(drizzleDb.schemas.backup)
.values({
status: "ongoing",
databaseId: database.id,
})
.returning();
} else {
backup = await db.query.backup.findFirst({
where: and(
eq(drizzleDb.schemas.backup.databaseId, database.id),
eq(drizzleDb.schemas.backup.status, "ongoing")
),
});
}
if (!backup) {
return res.status(404).json({error: "Backup not found"});
}
if (status === "success") {
const decipher = createDecryptionStream(encryptedAesKeyHex, ivHex);
const fileExt = extension || getFileExtension(database.dbms);
const fileName = `${uuidv4()}${fileExt}`;
const decryptedStream = req.pipe(decipher);
await uploadTempFileToProviders(backup, database, decryptedStream, fileName);
await sendNotificationsBackupRestore(database, "success_backup");
eventEmitter.emit('modification', {update: true});
return res.json({success: true});
} else {
await db
.update(drizzleDb.schemas.backup)
.set(withUpdatedAt({status: 'failed'}))
.where(eq(drizzleDb.schemas.backup.id, backup.id));
eventEmitter.emit('modification', {update: true});
await sendNotificationsBackupRestore(database, "error_backup");
return res.status(200).json({
error: "Backup successfully updated with status failed",
});
}
} catch (err: any) {
console.error("Upload error:", err);
return res.status(500).json({
error: "Upload failed",
detail: err.message,
});
}
});
export default router;
+3
View File
@@ -0,0 +1,3 @@
import { EventEmitter } from "events";
export const eventEmitter = new EventEmitter();
@@ -24,18 +24,27 @@ export async function uploadGoogleDrive(
? await ensureFolderPath(client, folderPath, config.folderId) ? await ensureFolderPath(client, folderPath, config.folderId)
: config.folderId; : config.folderId;
const existing = await findFileByName(client, fileName, folderId); const existing = await findFileByName(client, fileName, folderId);
if (existing) return {success: false, provider: "google-drive", error: "File already exists"}; if (existing) return {success: false, provider: "google-drive", error: "File already exists"};
let fileStream: Readable;
const file = input.data.file;
if (Buffer.isBuffer(file) || file instanceof Uint8Array) {
fileStream = Readable.from(file);
} else if ((file as any).pipe) {
fileStream = file as Readable;
} else {
throw new Error("Unsupported file type for streaming upload");
}
await client.files.create({ await client.files.create({
requestBody: {name: fileName, parents: [folderId]}, requestBody: {name: fileName, parents: [folderId]},
media: {body: Readable.from(input.data.file as Buffer)}, media: {body: fileStream},
fields: "id", fields: "id",
supportsAllDrives: true, supportsAllDrives: true,
}); });
if (input.data.url) { if (input.data.url) {
const url = await generateFileUrl(input); const url = await generateFileUrl(input);
if (!url) { if (!url) {
@@ -68,11 +77,15 @@ export async function getGoogleDrive(
const fileId = await resolveFilePath(client, input.data.path, config.folderId); const fileId = await resolveFilePath(client, input.data.path, config.folderId);
if (!fileId) return {success: false, provider: "google-drive", error: "File not found"}; if (!fileId) return {success: false, provider: "google-drive", error: "File not found"};
const res = await client.files.get( const res = await client.files.get(
{fileId, alt: "media", supportsAllDrives: true}, {fileId, alt: "media", supportsAllDrives: true},
{responseType: "arraybuffer"} {responseType: "stream"}
); );
const stream = res.data as Readable;
if (input.data.signedUrl) { if (input.data.signedUrl) {
const url = await generateFileUrl(input); const url = await generateFileUrl(input);
@@ -88,7 +101,7 @@ export async function getGoogleDrive(
return { return {
success: true, success: true,
provider: "google-drive", provider: "google-drive",
file: Buffer.from(res.data as ArrayBuffer), file: stream,
url: url, url: url,
}; };
} }
@@ -96,7 +109,7 @@ export async function getGoogleDrive(
return { return {
success: true, success: true,
provider: "google-drive", provider: "google-drive",
file: Buffer.from(res.data as ArrayBuffer), file: stream,
}; };
} }
@@ -1,9 +1,3 @@
// export type GoogleDriveConfig = {
// clientEmail: string;
// privateKey: string;
// folderId: string;
// };
export type GoogleDriveConfig = { export type GoogleDriveConfig = {
clientId: string; clientId: string;
+55 -41
View File
@@ -1,95 +1,109 @@
"use server" "use server"
import {mkdir, writeFile, unlink, readFile} from 'fs/promises'; import {mkdir, unlink} from 'fs/promises';
import path from 'path'; import path from 'path';
import {StorageDeleteInput, StorageGetInput, StorageMetaData, StorageResult, StorageUploadInput} from '../types'; import {StorageDeleteInput, StorageGetInput, StorageMetaData, StorageResult, StorageUploadInput} from '../types';
import fs from "node:fs"; import fs from "node:fs";
import {getServerUrl} from "@/utils/get-server-url";
import {generateFileUrl} from "@/features/storages/helpers"; import {generateFileUrl} from "@/features/storages/helpers";
import {Readable} from "node:stream";
const BASE_DIR = "/private/uploads/"; const BASE_DIR = "/private/uploads/";
export async function uploadLocal( export async function uploadLocal(
config: { baseDir?: string }, config: { baseDir?: string },
input: { data: StorageUploadInput, metadata?: StorageMetaData } input: { data: StorageUploadInput; metadata?: StorageMetaData }
): Promise<StorageResult> { ): Promise<StorageResult> {
const base = config.baseDir || BASE_DIR; const base = config.baseDir || BASE_DIR;
const fullPath = path.join(process.cwd(), base, input.data.path); const fullPath = path.join(process.cwd(), base, input.data.path);
const dir = path.dirname(fullPath); const dir = path.dirname(fullPath);
await mkdir(dir, {recursive: true}); await mkdir(dir, { recursive: true });
await writeFile(fullPath, input.data.file);
try {
if (input.data.url) { const file = input.data.file;
const url = await generateFileUrl(input); if (Buffer.isBuffer(file)) {
if (!url) { await fs.promises.writeFile(fullPath, input.data.file);
return { } else if (file instanceof Readable) {
success: false, await new Promise<void>((resolve, reject) => {
provider: "local", const writable = fs.createWriteStream(fullPath);
response: "Unable to get url file" file.pipe(writable);
}; writable.on("finish", resolve);
writable.on("error", reject);
});
} else {
return { success: false, provider: "local", error: "Unsupported file type. Must be Buffer or ReadableStream" };
} }
return {
success: true, if (input.data.url) {
provider: 'local', const url = await generateFileUrl(input);
url: url if (!url) {
}; return { success: false, provider: "local", response: "Unable to get URL" };
}
return { success: true, provider: "local", url };
}
return { success: true, provider: "local" };
} catch (err: any) {
try { await unlink(fullPath); } catch {}
return { success: false, provider: "local", error: err.message || "Upload failed" };
} }
return {
success: true,
provider: 'local',
};
} }
export async function getLocal( export async function getLocal(
config: { baseDir?: string }, config: { baseDir?: string },
input: { data: StorageGetInput, metadata: StorageMetaData } input: { data: StorageGetInput; metadata: StorageMetaData }
): Promise<StorageResult> { ): Promise<StorageResult> {
const base = config.baseDir || BASE_DIR; const base = config.baseDir || BASE_DIR;
const filePath = path.join(process.cwd(), base, input.data.path) const filePath = path.join(process.cwd(), base, input.data.path);
const fileName = path.basename(input.data.path);
const file = await readFile(filePath);
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
console.error("File not found at:", filePath); console.error("File not found at:", filePath);
return ({ return {
success: false, success: false,
provider: 'local', provider: "local",
}); error: "File not found",
};
}
let fileStream: fs.ReadStream | undefined;
try {
fileStream = fs.createReadStream(filePath);
} catch (err: any) {
console.error("Error creating read stream:", err);
return {
success: false,
provider: "local",
error: err.message,
};
} }
if (input.data.signedUrl) { if (input.data.signedUrl) {
const url = await generateFileUrl(input); const url = await generateFileUrl(input);
if (!url) { if (!url) {
return { return {
success: false, success: false,
provider: "local", provider: "local",
response: "Unable to get url file" error: "Unable to generate signed URL",
}; };
} }
return { return {
success: true, success: true,
provider: "local", provider: "local",
file: file, file: fileStream,
url: url, url,
}; };
} }
return { return {
success: true, success: true,
provider: "local", provider: "local",
file: file, file: fileStream,
}; };
} }
export async function deleteLocal( export async function deleteLocal(
config: { baseDir?: string }, config: { baseDir?: string },
input: { data: StorageDeleteInput, metadata?: StorageMetaData } input: { data: StorageDeleteInput, metadata?: StorageMetaData }
+37 -39
View File
@@ -1,6 +1,6 @@
import * as Minio from "minio"; import * as Minio from "minio";
import {StorageDeleteInput, StorageGetInput, StorageMetaData, StorageResult, StorageUploadInput} from "../types"; import {StorageDeleteInput, StorageGetInput, StorageMetaData, StorageResult, StorageUploadInput} from "../types";
import {generateFileUrl} from "@/features/storages/helpers"; import {Readable} from "node:stream";
type S3Config = { type S3Config = {
endPointUrl: string; endPointUrl: string;
@@ -32,72 +32,70 @@ async function ensureBucket(config: S3Config) {
export async function uploadS3( export async function uploadS3(
config: S3Config, config: S3Config,
input: { data: StorageUploadInput, metadata?: StorageMetaData } input: { data: StorageUploadInput, metadata?: StorageMetaData }
): Promise<StorageResult> { ): Promise<StorageResult> {
const client = await getS3Client(config); const client = await getS3Client(config);
console.log(client);
await ensureBucket(config); await ensureBucket(config);
const key = `${BASE_DIR}${input.data.path}`; 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");
}
try { try {
await client.statObject(config.bucketName, key); const result = await client.putObject(config.bucketName, key, uploadStream, input.data.size);
return {success: false, provider: "s3", error: "File already exists"}; console.log(result);
} catch { } catch (err: any) {
// continue if not found return {success: false, provider: "s3", error: err.message};
} }
await client.putObject(config.bucketName, key, input.data.file as Buffer); return {success: true, provider: "s3"};
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 {
success: true,
provider: 's3',
};
} }
export async function getS3(config: S3Config, input: { data: StorageGetInput, metadata: StorageMetaData }): Promise<StorageResult> {
const client = await getS3Client(config);
export async function getS3(
config: S3Config,
input: { data: StorageGetInput, metadata: StorageMetaData }
): Promise<StorageResult> {
const client = await getS3Client(config);
const key = `${BASE_DIR}${input.data.path}`; const key = `${BASE_DIR}${input.data.path}`;
try { try {
await client.statObject(config.bucketName, key); await client.statObject(config.bucketName, key);
} catch { } catch {
return {success: false, provider: "s3", error: "File not found"}; return {success: false, provider: "s3", error: "File not found"};
} }
const presignedUrl = await client.presignedGetObject(config.bucketName, key, 60);
const fileStream = await client.getObject(config.bucketName, key); const fileStream = await client.getObject(config.bucketName, key);
const chunks: Buffer[] = [];
for await (const chunk of fileStream) chunks.push(chunk as Buffer); let presignedUrl: string | undefined;
const buffer = Buffer.concat(chunks); if (input.data.signedUrl) {
presignedUrl = await client.presignedGetObject(config.bucketName, key, input.data.expiresInSeconds ?? 60);
}
return { return {
success: true, success: true,
provider: "s3", provider: "s3",
file: buffer, file: fileStream as unknown as Buffer | Readable,
url: presignedUrl, url: presignedUrl,
}; };
} }
export async function deleteS3(config: S3Config, input: { data: StorageDeleteInput, metadata?: StorageMetaData }): 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 = `${BASE_DIR}${input.data.path}`; const key = `${BASE_DIR}${input.data.path}`;
+6 -3
View File
@@ -1,3 +1,5 @@
import {Readable} from "node:stream";
export type StorageProviderKind = export type StorageProviderKind =
| 'local' | 'local'
| 's3' | 's3'
@@ -20,9 +22,10 @@ export type StorageMetaData = {
export interface StorageUploadInput { export interface StorageUploadInput {
path: string; path: string;
file: Buffer | Uint8Array; file: Readable | Buffer | Uint8Array;
url?: boolean; url?: boolean;
contentType?: string; contentType?: string;
size?: number;
} }
export interface StorageGetInput { export interface StorageGetInput {
@@ -37,7 +40,7 @@ export interface StorageDeleteInput {
export type StorageInput = export type StorageInput =
| { action: 'upload'; data: StorageUploadInput, metadata?: StorageMetaData } | { action: 'upload'; data: StorageUploadInput, metadata?: StorageMetaData }
| { action: 'get'; data: StorageGetInput, metadata: StorageMetaData} | { action: 'get'; data: StorageGetInput, metadata: StorageMetaData }
| { action: 'delete'; data: StorageDeleteInput, metadata?: StorageMetaData } | { action: 'delete'; data: StorageDeleteInput, metadata?: StorageMetaData }
| { action: 'ping'; }; | { action: 'ping'; };
@@ -45,7 +48,7 @@ export interface StorageResult {
success: boolean; success: boolean;
provider: StorageProviderKind | null; provider: StorageProviderKind | null;
url?: string; url?: string;
file?: Buffer; file?: Buffer | Readable;
error?: string; error?: string;
response?: any; response?: any;
} }
-1
View File
@@ -517,7 +517,6 @@ export const getActiveMember = async () => {
const member = await auth.api.getActiveMember({ const member = await auth.api.getActiveMember({
headers: await headers(), headers: await headers(),
}); });
console.log(member);
return member as MemberWithUser; return member as MemberWithUser;
} catch (e) { } catch (e) {
+1
View File
@@ -1,4 +1,5 @@
import nodemailer from "nodemailer"; import nodemailer from "nodemailer";
import {Server} from "./types"
export const createTransporter = (server: Server) => { export const createTransporter = (server: Server) => {
const portNumber = Number(server.port); const portNumber = Number(server.port);
+1
View File
@@ -3,6 +3,7 @@ import {db} from "@/db";
import {eq} from "drizzle-orm"; import {eq} from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {createTransporter} from "@/lib/email/helpers"; import {createTransporter} from "@/lib/email/helpers";
import {Payload} from "@/lib/email/types";
export const sendEmail = async (data: Payload) => { export const sendEmail = async (data: Payload) => {
const settings = await db const settings = await db
+2 -2
View File
@@ -1,14 +1,14 @@
"use server"; "use server";
type Payload = { export type Payload = {
to: string; to: string;
from?: string; from?: string;
subject: string; subject: string;
html: any; html: any;
}; };
type Server = { export type Server = {
host: string; host: string;
port: number; port: number;
user: string; user: string;
+1 -1
View File
@@ -5,7 +5,7 @@ import {enforceRetentionGFS} from "@/lib/tasks/database/retention-gsf";
import {retentionPolicy} from "@/db/schema/07_database"; import {retentionPolicy} from "@/db/schema/07_database";
import {isNull} from "drizzle-orm"; import {isNull} from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {eventEmitter} from "../../../../app/api/events/route"; import {eventEmitter} from "@/features/shared/event";
export const retentionCleanTask = async () => { export const retentionCleanTask = async () => {
+3 -3
View File
@@ -19,9 +19,9 @@ async function getS3Client() {
} }
const baseConfig = { const baseConfig = {
endPoint: settings.s3EndPointUrl ?? "", endPoint: settings?.s3EndPointUrl ?? "",
accessKey: settings.s3AccessKeyId ?? "", accessKey: settings?.s3AccessKeyId ?? "",
secretKey: settings.s3SecretAccessKey ?? "", secretKey: settings?.s3SecretAccessKey ?? "",
}; };
return new Minio.Client({ return new Minio.Client({