After DAL refactor
This commit is contained in:
parent
2f712dba23
commit
e15373c169
22 changed files with 4904 additions and 1019 deletions
File diff suppressed because it is too large
Load diff
Binary file not shown.
599
package-lock.json
generated
599
package-lock.json
generated
|
|
@ -22,7 +22,8 @@
|
||||||
"next-auth": "^5.0.0-beta.30",
|
"next-auth": "^5.0.0-beta.30",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3"
|
"react-dom": "19.2.3",
|
||||||
|
"server-only": "^0.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "^11.0.0",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.1",
|
"eslint-config-next": "16.1.1",
|
||||||
"prisma": "^7.2.0",
|
"prisma": "^6.19.2",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -345,73 +346,6 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@chevrotain/cst-dts-gen": {
|
|
||||||
"version": "10.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz",
|
|
||||||
"integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@chevrotain/gast": "10.5.0",
|
|
||||||
"@chevrotain/types": "10.5.0",
|
|
||||||
"lodash": "4.17.21"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@chevrotain/gast": {
|
|
||||||
"version": "10.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz",
|
|
||||||
"integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@chevrotain/types": "10.5.0",
|
|
||||||
"lodash": "4.17.21"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@chevrotain/types": {
|
|
||||||
"version": "10.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz",
|
|
||||||
"integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/@chevrotain/utils": {
|
|
||||||
"version": "10.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz",
|
|
||||||
"integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/@electric-sql/pglite": {
|
|
||||||
"version": "0.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz",
|
|
||||||
"integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/@electric-sql/pglite-socket": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz",
|
|
||||||
"integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"pglite-server": "dist/scripts/server.js"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@electric-sql/pglite": "0.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electric-sql/pglite-tools": {
|
|
||||||
"version": "0.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz",
|
|
||||||
"integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@electric-sql/pglite": "0.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.0.tgz",
|
||||||
|
|
@ -741,19 +675,6 @@
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@hono/node-server": {
|
|
||||||
"version": "1.19.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz",
|
|
||||||
"integrity": "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.14.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"hono": "^4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
|
|
@ -1318,20 +1239,6 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mrleebo/prisma-ast": {
|
|
||||||
"version": "0.12.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz",
|
|
||||||
"integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"chevrotain": "^10.5.0",
|
|
||||||
"lilconfig": "^2.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/core-downloads-tracker": {
|
"node_modules/@mui/core-downloads-tracker": {
|
||||||
"version": "7.3.7",
|
"version": "7.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.7.tgz",
|
||||||
|
|
@ -1960,9 +1867,9 @@
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/config": {
|
"node_modules/@prisma/config": {
|
||||||
"version": "7.2.0",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.2.tgz",
|
||||||
"integrity": "sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ==",
|
"integrity": "sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1978,32 +1885,6 @@
|
||||||
"integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
|
"integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/dev": {
|
|
||||||
"version": "0.17.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.17.0.tgz",
|
|
||||||
"integrity": "sha512-6sGebe5jxX+FEsQTpjHLzvOGPn6ypFQprcs3jcuIWv1Xp/5v6P/rjfdvAwTkP2iF6pDx2tCd8vGLNWcsWzImTA==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@electric-sql/pglite": "0.3.2",
|
|
||||||
"@electric-sql/pglite-socket": "0.0.6",
|
|
||||||
"@electric-sql/pglite-tools": "0.2.7",
|
|
||||||
"@hono/node-server": "1.19.6",
|
|
||||||
"@mrleebo/prisma-ast": "0.12.1",
|
|
||||||
"@prisma/get-platform": "6.8.2",
|
|
||||||
"@prisma/query-plan-executor": "6.18.0",
|
|
||||||
"foreground-child": "3.3.1",
|
|
||||||
"get-port-please": "3.1.2",
|
|
||||||
"hono": "4.10.6",
|
|
||||||
"http-status-codes": "2.3.0",
|
|
||||||
"pathe": "2.0.3",
|
|
||||||
"proper-lockfile": "4.1.2",
|
|
||||||
"remeda": "2.21.3",
|
|
||||||
"std-env": "3.9.0",
|
|
||||||
"valibot": "1.2.0",
|
|
||||||
"zeptomatch": "2.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@prisma/driver-adapter-utils": {
|
"node_modules/@prisma/driver-adapter-utils": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz",
|
||||||
|
|
@ -2014,94 +1895,69 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines": {
|
"node_modules/@prisma/engines": {
|
||||||
"version": "7.2.0",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.2.tgz",
|
||||||
"integrity": "sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA==",
|
"integrity": "sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "7.2.0",
|
"@prisma/debug": "6.19.2",
|
||||||
"@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
"@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
"@prisma/fetch-engine": "7.2.0",
|
"@prisma/fetch-engine": "6.19.2",
|
||||||
"@prisma/get-platform": "7.2.0"
|
"@prisma/get-platform": "6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines-version": {
|
"node_modules/@prisma/engines-version": {
|
||||||
"version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
"version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz",
|
||||||
"integrity": "sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA==",
|
"integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
|
"node_modules/@prisma/engines/node_modules/@prisma/debug": {
|
||||||
"version": "7.2.0",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.2.tgz",
|
||||||
"integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
|
"integrity": "sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0"
|
||||||
"dependencies": {
|
|
||||||
"@prisma/debug": "7.2.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/fetch-engine": {
|
"node_modules/@prisma/fetch-engine": {
|
||||||
"version": "7.2.0",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.2.tgz",
|
||||||
"integrity": "sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w==",
|
"integrity": "sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "7.2.0",
|
"@prisma/debug": "6.19.2",
|
||||||
"@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
"@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
"@prisma/get-platform": "7.2.0"
|
"@prisma/get-platform": "6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
|
"node_modules/@prisma/fetch-engine/node_modules/@prisma/debug": {
|
||||||
"version": "7.2.0",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.2.tgz",
|
||||||
"integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
|
"integrity": "sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0"
|
||||||
"dependencies": {
|
|
||||||
"@prisma/debug": "7.2.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/get-platform": {
|
"node_modules/@prisma/get-platform": {
|
||||||
"version": "6.8.2",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.2.tgz",
|
||||||
"integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==",
|
"integrity": "sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.8.2"
|
"@prisma/debug": "6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
|
"node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
|
||||||
"version": "6.8.2",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.2.tgz",
|
||||||
"integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==",
|
"integrity": "sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/query-plan-executor": {
|
|
||||||
"version": "6.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz",
|
|
||||||
"integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/@prisma/studio-core": {
|
|
||||||
"version": "0.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.9.0.tgz",
|
|
||||||
"integrity": "sha512-xA2zoR/ADu/NCSQuriBKTh6Ps4XjU0bErkEcgMfnSGh346K1VI7iWKnoq1l2DoxUqiddPHIEWwtxJ6xCHG6W7g==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^18.0.0 || ^19.0.0",
|
|
||||||
"react": "^18.0.0 || ^19.0.0",
|
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
|
|
@ -3023,16 +2879,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-ssl-profiles": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/axe-core": {
|
"node_modules/axe-core": {
|
||||||
"version": "4.11.0",
|
"version": "4.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz",
|
||||||
|
|
@ -3280,21 +3126,6 @@
|
||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chevrotain": {
|
|
||||||
"version": "10.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz",
|
|
||||||
"integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@chevrotain/cst-dts-gen": "10.5.0",
|
|
||||||
"@chevrotain/gast": "10.5.0",
|
|
||||||
"@chevrotain/types": "10.5.0",
|
|
||||||
"@chevrotain/utils": "10.5.0",
|
|
||||||
"lodash": "4.17.21",
|
|
||||||
"regexp-to-ast": "0.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
|
|
@ -3407,7 +3238,7 @@
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
|
|
@ -3562,16 +3393,6 @@
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/denque": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/destr": {
|
"node_modules/destr": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||||
|
|
@ -4529,23 +4350,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/foreground-child": {
|
|
||||||
"version": "3.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
|
||||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"cross-spawn": "^7.0.6",
|
|
||||||
"signal-exit": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
|
@ -4586,16 +4390,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/generate-function": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-property": "^1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/generator-function": {
|
"node_modules/generator-function": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
|
||||||
|
|
@ -4641,13 +4435,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-port-please": {
|
|
||||||
"version": "3.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz",
|
|
||||||
"integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/get-proto": {
|
"node_modules/get-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
|
@ -4767,20 +4554,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/graceful-fs": {
|
|
||||||
"version": "4.2.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/grammex": {
|
|
||||||
"version": "3.1.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
|
|
||||||
"integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/has-bigints": {
|
"node_modules/has-bigints": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||||
|
|
@ -4900,40 +4673,6 @@
|
||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hono": {
|
|
||||||
"version": "4.10.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.10.6.tgz",
|
|
||||||
"integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/http-status-codes": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/iconv-lite": {
|
|
||||||
"version": "0.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
|
|
||||||
"integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/express"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
|
|
@ -5260,13 +4999,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-property": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/is-regex": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
|
|
@ -5423,7 +5155,7 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/iterator.prototype": {
|
"node_modules/iterator.prototype": {
|
||||||
|
|
@ -5594,16 +5326,6 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lilconfig": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lines-and-columns": {
|
"node_modules/lines-and-columns": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
|
|
@ -5626,13 +5348,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
|
@ -5640,13 +5355,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/long": {
|
|
||||||
"version": "5.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
|
||||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/loose-envify": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
|
@ -5669,22 +5377,6 @@
|
||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lru.min": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"bun": ">=1.0.0",
|
|
||||||
"deno": ">=1.30.0",
|
|
||||||
"node": ">=8.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/wellwelwel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
|
@ -5748,40 +5440,6 @@
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/mysql2": {
|
|
||||||
"version": "3.15.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
|
|
||||||
"integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"aws-ssl-profiles": "^1.1.1",
|
|
||||||
"denque": "^2.1.0",
|
|
||||||
"generate-function": "^2.3.1",
|
|
||||||
"iconv-lite": "^0.7.0",
|
|
||||||
"long": "^5.2.1",
|
|
||||||
"lru.min": "^1.0.0",
|
|
||||||
"named-placeholders": "^1.1.3",
|
|
||||||
"seq-queue": "^0.0.5",
|
|
||||||
"sqlstring": "^2.3.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/named-placeholders": {
|
|
||||||
"version": "1.1.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
|
|
||||||
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"lru.min": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
|
|
@ -6187,7 +5845,7 @@
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -6389,20 +6047,6 @@
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postgres": {
|
|
||||||
"version": "3.4.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
|
|
||||||
"integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "Unlicense",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/porsager"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postgres-array": {
|
"node_modules/postgres-array": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
|
||||||
|
|
@ -6472,34 +6116,26 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prisma": {
|
"node_modules/prisma": {
|
||||||
"version": "7.2.0",
|
"version": "6.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.2.tgz",
|
||||||
"integrity": "sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg==",
|
"integrity": "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/config": "7.2.0",
|
"@prisma/config": "6.19.2",
|
||||||
"@prisma/dev": "0.17.0",
|
"@prisma/engines": "6.19.2"
|
||||||
"@prisma/engines": "7.2.0",
|
|
||||||
"@prisma/studio-core": "0.9.0",
|
|
||||||
"mysql2": "3.15.3",
|
|
||||||
"postgres": "3.4.7"
|
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"prisma": "build/index.js"
|
"prisma": "build/index.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19 || ^22.12 || >=24.0"
|
"node": ">=18.18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"better-sqlite3": ">=9.0.0",
|
"typescript": ">=5.1.0"
|
||||||
"typescript": ">=5.4.0"
|
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"better-sqlite3": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
|
@ -6516,25 +6152,6 @@
|
||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proper-lockfile": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.4",
|
|
||||||
"retry": "^0.12.0",
|
|
||||||
"signal-exit": "^3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/proper-lockfile/node_modules/signal-exit": {
|
|
||||||
"version": "3.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|
@ -6674,13 +6291,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/regexp-to-ast": {
|
|
||||||
"version": "0.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
|
|
||||||
"integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
|
|
@ -6702,16 +6312,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/remeda": {
|
|
||||||
"version": "2.21.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz",
|
|
||||||
"integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"type-fest": "^4.39.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/reselect": {
|
"node_modules/reselect": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
|
@ -6757,16 +6357,6 @@
|
||||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/retry": {
|
|
||||||
"version": "0.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
|
||||||
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/reusify": {
|
"node_modules/reusify": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||||
|
|
@ -6857,13 +6447,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/safer-buffer": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
|
|
@ -6880,11 +6463,11 @@
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/seq-queue": {
|
"node_modules/server-only": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
|
||||||
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
|
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
|
||||||
"devOptional": true
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/set-function-length": {
|
"node_modules/set-function-length": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
|
|
@ -6997,7 +6580,7 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shebang-regex": "^3.0.0"
|
"shebang-regex": "^3.0.0"
|
||||||
|
|
@ -7010,7 +6593,7 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -7092,19 +6675,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/signal-exit": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
|
@ -7132,16 +6702,6 @@
|
||||||
"node": ">= 10.x"
|
"node": ">= 10.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sqlstring": {
|
|
||||||
"version": "2.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
|
||||||
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/stable-hash": {
|
"node_modules/stable-hash": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
|
||||||
|
|
@ -7149,13 +6709,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/std-env": {
|
|
||||||
"version": "3.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
|
|
||||||
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/stop-iteration-iterator": {
|
"node_modules/stop-iteration-iterator": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
||||||
|
|
@ -7489,19 +7042,6 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/type-fest": {
|
|
||||||
"version": "4.41.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
|
||||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "(MIT OR CC0-1.0)",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/typed-array-buffer": {
|
"node_modules/typed-array-buffer": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||||
|
|
@ -7729,26 +7269,11 @@
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/valibot": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": ">=5"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"typescript": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
|
|
@ -7897,16 +7422,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zeptomatch": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==",
|
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"grammex": "^3.1.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
"next-auth": "^5.0.0-beta.30",
|
"next-auth": "^5.0.0-beta.30",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3"
|
"react-dom": "19.2.3",
|
||||||
|
"server-only": "^0.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
|
@ -36,7 +37,7 @@
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "^11.0.0",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.1",
|
"eslint-config-next": "16.1.1",
|
||||||
"prisma": "^7.2.0",
|
"prisma": "^6.19.2",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export default defineConfig({
|
||||||
schema: "prisma/schema.prisma",
|
schema: "prisma/schema.prisma",
|
||||||
datasource: {
|
datasource: {
|
||||||
// This is now the ONLY place where the DB connection string is defined
|
// This is now the ONLY place where the DB connection string is defined
|
||||||
url: process.env.DATABASE_URL,
|
// prisma.config.ts
|
||||||
|
url: process.env.DATABASE_URL!,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -1,66 +1,57 @@
|
||||||
// src/app/api/download/route.ts
|
// src/app/api/download/route.ts
|
||||||
"use server"
|
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { auth } from "@/auth"; // Import the auth function from your central config
|
import { auth } from "@/auth";
|
||||||
import { prisma } from '@/lib/prisma';
|
import { getFileNodeById } from "@/data-access/file-nodes";
|
||||||
|
import { getOneDriveContentStream } from "@/services/onedrive";
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
// 1. Check Authentication using the V5 auth() helper
|
// 1. Authenticate the user session
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
if (!session?.user?.id) {
|
||||||
// In V5, tokens are usually handled in the session callback
|
return new NextResponse("Unauthorized", { status: 401 });
|
||||||
if (!session || !session.accessToken) {
|
|
||||||
return new NextResponse("Unauthorized - No Access Token found", { status: 401 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Get parameters from URL
|
// 2. Extract parameters from URL
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const fileNodeId = searchParams.get('id');
|
const fileId = searchParams.get('id');
|
||||||
const mode = searchParams.get('mode') === 'inline' ? 'inline' : 'attachment';
|
const mode = searchParams.get('mode') === 'inline' ? 'inline' : 'attachment';
|
||||||
|
|
||||||
if (!fileNodeId) {
|
if (!fileId) {
|
||||||
return new NextResponse("File ID is required", { status: 400 });
|
return new NextResponse("File ID is required", { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Find the file in your Postgres FileNode table
|
// 3. DAL: Fetch file metadata from local database
|
||||||
const fileNode = await prisma.fileNode.findUnique({
|
const fileNode = await getFileNodeById(fileId);
|
||||||
where: { id: fileNodeId }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!fileNode || !fileNode.oneDriveId) {
|
if (!fileNode || !fileNode.oneDriveId) {
|
||||||
return new NextResponse("File not found in database", { status: 404 });
|
return new NextResponse("File not found", { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Fetch the file stream from Microsoft Graph
|
// 4. SERVICE: Get the binary stream from Microsoft Graph
|
||||||
const graphResponse = await fetch(
|
// The service layer automatically handles the 'getFreshAccessToken' logic
|
||||||
`https://graph.microsoft.com/v1.0/me/drive/items/${fileNode.oneDriveId}/content`,
|
const graphResponse = await getOneDriveContentStream(session.user.id, fileNode.oneDriveId);
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${session.accessToken}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!graphResponse.ok) {
|
|
||||||
const errorText = await graphResponse.text();
|
|
||||||
console.error('MS Graph Error:', errorText);
|
|
||||||
return new NextResponse(`OneDrive Error: ${graphResponse.statusText}`, { status: graphResponse.status });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Stream the response directly to the client
|
// 5. Stream the response directly to the client
|
||||||
return new NextResponse(graphResponse.body, {
|
// We pass the graphResponse.body (ReadableStream) directly to NextResponse
|
||||||
status: 200,
|
// src/app/api/download/route.ts
|
||||||
headers: {
|
const metadata = fileNode.metadata as any;
|
||||||
'Content-Type': fileNode.mimeType || 'application/octet-stream',
|
const contentType = metadata?.mimeType || 'application/octet-stream';
|
||||||
// Note: Using encodeURIComponent for filename to handle special characters
|
|
||||||
'Content-Disposition': `${mode}; filename="${encodeURIComponent(fileNode.name)}"`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
return new NextResponse(graphResponse.body, {
|
||||||
console.error('Download error:', error);
|
status: 200,
|
||||||
return new NextResponse("Internal Server Error", { status: 500 });
|
headers: {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Content-Disposition': `${mode}; filename="${encodeURIComponent(fileNode.name)}"`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Download Route Error:', error);
|
||||||
|
return new NextResponse(
|
||||||
|
JSON.stringify({ error: "Internal Server Error", message: error.message }),
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,138 +1,82 @@
|
||||||
|
// src/app/dashboard/actions.ts
|
||||||
|
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { getFreshAccessToken } from "@/lib/auth-utils";
|
import {
|
||||||
|
getAllFileNodes,
|
||||||
|
getFileNodeById,
|
||||||
|
updateFileNode,
|
||||||
|
deleteFileNode
|
||||||
|
} from "@/data-access/file-nodes";
|
||||||
|
import {
|
||||||
|
getOneDriveItem,
|
||||||
|
deleteFromOneDrive,
|
||||||
|
uploadToOneDrive
|
||||||
|
} from "@/services/onedrive";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. FETCH: Get all file nodes for the Dashboard
|
* 1. FETCH: Get all file nodes
|
||||||
|
* Now simply calls the DAL. Error handling is left to the caller (the UI).
|
||||||
*/
|
*/
|
||||||
export async function getFileNodes() {
|
export async function getFileNodes() {
|
||||||
try {
|
return await getAllFileNodes();
|
||||||
const nodes = await prisma.fileNode.findMany({
|
|
||||||
orderBy: {
|
|
||||||
updatedAt: 'desc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return nodes;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching file nodes:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2. DOWNLOAD: Generates the authenticated OneDrive URL
|
* 2. DOWNLOAD: Generates the authenticated OneDrive URL
|
||||||
|
* Orchestrates the session check, DAL lookup, and Service call.
|
||||||
*/
|
*/
|
||||||
export async function getDownloadUrlAction(id: string) {
|
export async function getDownloadUrlAction(id: string) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
||||||
|
|
||||||
const file = await prisma.fileNode.findUnique({ where: { id } });
|
const file = await getFileNodeById(id);
|
||||||
if (!file || !file.oneDriveId) throw new Error("File not found or missing cloud ID");
|
if (!file || !file.oneDriveId) throw new Error("File not found or missing cloud ID");
|
||||||
|
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
// Service handles token refresh and graph request internally
|
||||||
const res = await fetch(
|
const data = await getOneDriveItem(session.user.id, file.oneDriveId);
|
||||||
`https://graph.microsoft.com/v1.0/me/drive/items/${file.oneDriveId}`,
|
|
||||||
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error("Failed to contact OneDrive");
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
const downloadUrl = data["@microsoft.graph.downloadUrl"];
|
const downloadUrl = data["@microsoft.graph.downloadUrl"];
|
||||||
|
|
||||||
if (!downloadUrl) throw new Error("OneDrive did not provide a download link");
|
if (!downloadUrl) throw new Error("OneDrive did not provide a download URL");
|
||||||
|
return downloadUrl;
|
||||||
return { downloadUrl };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3. DELETE: Remove from OneDrive (via ID) and Database
|
* 3. DELETE: Removes from both Cloud and Database
|
||||||
* Folders are virtual (DB only), so cloud deletion is skipped if oneDriveId is null.
|
|
||||||
*/
|
*/
|
||||||
export async function deleteFileAction(fileId: string) {
|
export async function deleteFileNodeAction(id: string) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
||||||
|
|
||||||
const node = await prisma.fileNode.findUnique({
|
|
||||||
where: { id: fileId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
revalidatePath("/dashboard");
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const isAdmin = session.user.role === "ADMIN";
|
|
||||||
const isOwner = node.ownerId === session.user.id;
|
|
||||||
|
|
||||||
if (!isAdmin && !isOwner) {
|
|
||||||
throw new Error("Permission Denied.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
const file = await getFileNodeById(id);
|
||||||
|
if (!file) throw new Error("File record not found");
|
||||||
|
|
||||||
// Only attempt cloud deletion if it's a file/storage with a oneDriveId.
|
// Phase 1: Cloud Deletion
|
||||||
// Virtual folders created in the DB have no oneDriveId and are skipped.
|
if (file.oneDriveId) {
|
||||||
if (accessToken && node.oneDriveId) {
|
await deleteFromOneDrive(session.user.id, file.oneDriveId);
|
||||||
const onedriveRes = await fetch(
|
|
||||||
`https://graph.microsoft.com/v1.0/me/drive/items/${node.oneDriveId}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: { Authorization: `Bearer ${accessToken}` },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!onedriveRes.ok && onedriveRes.status !== 404) {
|
|
||||||
console.warn("OneDrive Deletion Warning: Cloud record might still exist.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (cloudError) {
|
|
||||||
console.error("Cloud cleanup failed:", cloudError);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Phase 2: Database Deletion
|
||||||
await prisma.fileNode.delete({ where: { id: fileId } });
|
await deleteFileNode(id);
|
||||||
revalidatePath("/dashboard");
|
|
||||||
revalidatePath("/upload");
|
|
||||||
return { success: true };
|
|
||||||
} catch (dbError) {
|
|
||||||
throw new Error("Failed to remove the record from the database.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 4. MOVE: Assign file to folder or folder to another folder (Virtual Move)
|
|
||||||
*/
|
|
||||||
export async function moveNodeAction(nodeId: string, newParentId: string | null) {
|
|
||||||
const session = await auth();
|
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
|
||||||
if (nodeId === newParentId) throw new Error("Cannot move to self.");
|
|
||||||
|
|
||||||
try {
|
|
||||||
await prisma.fileNode.update({
|
|
||||||
where: { id: nodeId },
|
|
||||||
data: { parentId: newParentId }
|
|
||||||
});
|
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error("Move failed.");
|
console.error("Delete Error:", error);
|
||||||
|
return { success: false, error: "Failed to delete file" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 5. UPDATE & REPLACE: Full update of metadata and OneDrive content
|
* 4. UPDATE: Modify record and optionally sync new content to OneDrive
|
||||||
*/
|
*/
|
||||||
export async function updateFileFullAction(formData: FormData) {
|
export async function updateFileNodeAction(id: string, formData: FormData) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
||||||
|
|
||||||
const id = formData.get("id") as string;
|
|
||||||
const name = formData.get("name") as string;
|
const name = formData.get("name") as string;
|
||||||
const description = formData.get("description") as string;
|
const description = formData.get("description") as string;
|
||||||
const parentIdRaw = formData.get("parentId") as string;
|
const parentIdRaw = formData.get("parentId") as string;
|
||||||
|
|
@ -143,44 +87,29 @@ export async function updateFileFullAction(formData: FormData) {
|
||||||
let metadata = JSON.parse(metadataStr);
|
let metadata = JSON.parse(metadataStr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
const node = await getFileNodeById(id);
|
||||||
const node = await prisma.fileNode.findUnique({ where: { id } });
|
|
||||||
|
|
||||||
// Update physical file content only if a new file is uploaded and we have a target oneDriveId
|
// If a new file is uploaded, push it to OneDrive first
|
||||||
if (newFile && newFile.size > 0 && node?.oneDriveId) {
|
if (newFile && newFile.size > 0 && node?.oneDriveId) {
|
||||||
const onedrivePath = `https://graph.microsoft.com/v1.0/me/drive/items/${node.oneDriveId}/content`;
|
await uploadToOneDrive(session.user.id, newFile, node.oneDriveId);
|
||||||
|
|
||||||
const uploadRes = await fetch(onedrivePath, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
"Content-Type": newFile.type
|
|
||||||
},
|
|
||||||
body: Buffer.from(await newFile.arrayBuffer()),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!uploadRes.ok) throw new Error("OneDrive content update failed");
|
|
||||||
|
|
||||||
metadata.type = newFile.name.split('.').pop()?.toUpperCase() || 'UNKNOWN';
|
metadata.type = newFile.name.split('.').pop()?.toUpperCase() || 'UNKNOWN';
|
||||||
metadata.mimeType = newFile.type;
|
metadata.mimeType = newFile.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.fileNode.update({
|
// Update the database via DAL
|
||||||
where: { id },
|
await updateFileNode(id, {
|
||||||
data: {
|
name,
|
||||||
name,
|
description,
|
||||||
description,
|
parentId,
|
||||||
parentId,
|
metadata,
|
||||||
metadata,
|
size: newFile ? BigInt(newFile.size) : undefined,
|
||||||
size: newFile ? BigInt(newFile.size) : undefined,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error("Full Update Failure:", error);
|
console.error("Update Error:", error);
|
||||||
throw new Error(error.message || "Failed to update record.");
|
return { success: false, error: "Failed to update record" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
// src/app/dashboard/dashboard-view.tsx
|
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -13,15 +11,15 @@ import {
|
||||||
Stack,
|
Stack,
|
||||||
TextField,
|
TextField,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
Tooltip
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
DataGrid,
|
DataGrid,
|
||||||
GridColDef,
|
GridColDef,
|
||||||
Toolbar,
|
GridToolbarContainer,
|
||||||
|
// Using the new non-deprecated QuickFilter components
|
||||||
QuickFilter,
|
QuickFilter,
|
||||||
QuickFilterControl,
|
QuickFilterControl,
|
||||||
QuickFilterClear,
|
|
||||||
} from "@mui/x-data-grid";
|
} from "@mui/x-data-grid";
|
||||||
import SyncIcon from "@mui/icons-material/Sync";
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||||
|
|
@ -30,51 +28,47 @@ import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import EditIcon from "@mui/icons-material/Edit";
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
import DownloadIcon from '@mui/icons-material/Download';
|
import DownloadIcon from '@mui/icons-material/Download';
|
||||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||||
|
|
||||||
import { syncOneDrive } from "./sync-actions";
|
import { syncOneDrive } from "./sync-actions";
|
||||||
import { deleteFileAction } from "./actions";
|
import { deleteFileNodeAction } from "./actions";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPDATED TOOLBAR: Uses the new QuickFilter structure to avoid deprecation
|
||||||
|
*/
|
||||||
function CustomToolbar() {
|
function CustomToolbar() {
|
||||||
return (
|
return (
|
||||||
<Toolbar sx={{ p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<GridToolbarContainer sx={{ p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Typography variant="h6" fontWeight="bold" color="primary">
|
<Typography variant="h6" fontWeight="bold" color="primary">
|
||||||
Library
|
Library
|
||||||
</Typography>
|
</Typography>
|
||||||
<QuickFilter sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<QuickFilterControl
|
<QuickFilter>
|
||||||
render={({ ref, ...controlProps }, state) => (
|
<QuickFilterControl
|
||||||
<TextField
|
render={(props) => (
|
||||||
{...controlProps}
|
<TextField
|
||||||
inputRef={ref}
|
{...props}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Search files and metadata..."
|
placeholder="Search files..."
|
||||||
sx={{ width: 350 }}
|
sx={{ width: 350 }}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
<SearchIcon fontSize="small" />
|
<SearchIcon fontSize="small" />
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
endAdornment: state.value ? (
|
},
|
||||||
<InputAdornment position="end">
|
}}
|
||||||
<QuickFilterClear size="small">
|
/>
|
||||||
<CancelIcon fontSize="small" />
|
)}
|
||||||
</QuickFilterClear>
|
/>
|
||||||
</InputAdornment>
|
</QuickFilter>
|
||||||
) : null,
|
</Box>
|
||||||
},
|
</GridToolbarContainer>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</QuickFilter>
|
|
||||||
</Toolbar>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,21 +115,18 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
||||||
const handleDelete = async (id: string, name: string) => {
|
const handleDelete = async (id: string, name: string) => {
|
||||||
if (!confirm(`Are you sure you want to delete "${name}"?`)) return;
|
if (!confirm(`Are you sure you want to delete "${name}"?`)) return;
|
||||||
try {
|
try {
|
||||||
await deleteFileAction(id);
|
await deleteFileNodeAction(id);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
alert(error.message || "Failed to delete file");
|
alert(error.message || "Failed to delete file");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- NEW DOWNLOAD FUNCTIONS ---
|
|
||||||
const handleDownload = (id: string) => {
|
const handleDownload = (id: string) => {
|
||||||
// Triggers local folder download via Content-Disposition: attachment
|
|
||||||
window.location.href = `/api/download?id=${id}&mode=attachment`;
|
window.location.href = `/api/download?id=${id}&mode=attachment`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewInTab = (id: string) => {
|
const handleViewInTab = (id: string) => {
|
||||||
// Opens in a new tab via Content-Disposition: inline
|
|
||||||
window.open(`/api/download?id=${id}&mode=inline`, '_blank');
|
window.open(`/api/download?id=${id}&mode=inline`, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -146,7 +137,7 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
||||||
flex: 1.5,
|
flex: 1.5,
|
||||||
minWidth: 250,
|
minWidth: 250,
|
||||||
renderCell: (params) => (
|
renderCell: (params) => (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, height: '100%' }}>
|
||||||
{params.row.isFolder ? <FolderIcon sx={{ color: '#FFB020' }} /> : <InsertDriveFileIcon color="action" />}
|
{params.row.isFolder ? <FolderIcon sx={{ color: '#FFB020' }} /> : <InsertDriveFileIcon color="action" />}
|
||||||
<Typography variant="body2">{params.value}</Typography>
|
<Typography variant="body2">{params.value}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -179,14 +170,14 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
||||||
{
|
{
|
||||||
field: "actions",
|
field: "actions",
|
||||||
headerName: "Actions",
|
headerName: "Actions",
|
||||||
width: 180, // Increased width to accommodate new buttons
|
width: 180,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
renderCell: (params) => {
|
renderCell: (params) => {
|
||||||
const isOwner = params.row.ownerId === user?.id;
|
const isOwner = params.row.ownerId === user?.id;
|
||||||
const isFolder = params.row.isFolder;
|
const isFolder = params.row.isFolder;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" spacing={0.5} justifyContent="flex-end">
|
<Stack direction="row" spacing={0.5} justifyContent="flex-end" alignItems="center" sx={{ height: '100%' }}>
|
||||||
{!isFolder && (
|
{!isFolder && (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="View in Tab">
|
<Tooltip title="View in Tab">
|
||||||
|
|
@ -227,28 +218,25 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "metadata_search",
|
|
||||||
headerName: "Metadata Search",
|
|
||||||
width: 0,
|
|
||||||
valueGetter: (value, row) => {
|
|
||||||
if (!row.metadata) return "";
|
|
||||||
return Object.entries(row.metadata)
|
|
||||||
.filter(([k]) => k !== 'type' && k !== 'mimeType')
|
|
||||||
.map(([k, v]) => `${k}:${v}`)
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="space-y-4">
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
|
||||||
<Button variant="outlined" startIcon={isRefreshing ? <CircularProgress size={16} /> : <RefreshIcon />} onClick={handleRefresh}>
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={isRefreshing ? <CircularProgress size={16} /> : <RefreshIcon />}
|
||||||
|
onClick={handleRefresh}
|
||||||
|
>
|
||||||
Refresh List
|
Refresh List
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" startIcon={loading ? <CircularProgress size={20} color="inherit" /> : <SyncIcon />} onClick={handleSync} disabled={loading}>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={loading ? <CircularProgress size={20} color="inherit" /> : <SyncIcon />}
|
||||||
|
onClick={handleSync}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
Sync OneDrive
|
Sync OneDrive
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -258,19 +246,11 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
||||||
rows={initialFiles}
|
rows={initialFiles}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
slots={{ toolbar: CustomToolbar }}
|
slots={{ toolbar: CustomToolbar }}
|
||||||
showToolbar
|
|
||||||
disableRowSelectionOnClick
|
disableRowSelectionOnClick
|
||||||
initialState={{
|
|
||||||
columns: {
|
|
||||||
columnVisibilityModel: {
|
|
||||||
metadata_search: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
sx={{
|
sx={{
|
||||||
border: 'none',
|
border: 'none',
|
||||||
'& .MuiDataGrid-columnHeaders': { bgcolor: '#f8f9fa' },
|
'& .MuiDataGrid-columnHeader': { bgcolor: '#f8f9fa' },
|
||||||
'& .MuiDataGrid-toolbarContainer': { borderBottom: '1px solid #eee' }
|
'& .MuiDataGrid-footerContainer': { borderTop: '1px solid #eee' }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,49 @@
|
||||||
|
// src/app/dashboard/sync-actions.ts
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { getFreshAccessToken } from "@/lib/auth-utils";
|
import { upsertFileNode } from "@/data-access/file-nodes";
|
||||||
|
import { getWebCalibreChildren } from "@/services/onedrive";
|
||||||
|
// src/app/dashboard/sync-actions.ts
|
||||||
|
//import { upsertFileNode } from "@/data-access/file-nodes"; // Change this from /services/onedrive
|
||||||
|
|
||||||
export async function syncOneDrive() {
|
export async function syncOneDrive() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
// 1. Call Service to get cloud data (token refresh handled inside service)
|
||||||
const response = await fetch("https://graph.microsoft.com/v1.0/me/drive/root:/WebCalibre:/children", {
|
const items = await getWebCalibreChildren(session.user.id);
|
||||||
headers: { Authorization: `Bearer ${accessToken}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) return { success: true, count: 0 };
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
let syncedCount = 0;
|
let syncedCount = 0;
|
||||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
|
|
||||||
for (const item of data.value) {
|
for (const item of items) {
|
||||||
const isFolder = !!item.folder;
|
const isFolder = !!item.folder;
|
||||||
|
|
||||||
// Only skip if it's a folder AND it's a UUID (storage container)
|
// Business Logic: Skip UUID storage folders
|
||||||
// If a user named a file with a UUID, we still want it.
|
if (isFolder && uuidRegex.test(item.name)) continue;
|
||||||
if (isFolder && uuidRegex.test(item.name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extension = isFolder ? 'FOLDER' : (item.name.split('.').pop()?.toUpperCase() || 'UNKNOWN');
|
const extension = isFolder ? 'FOLDER' : (item.name.split('.').pop()?.toUpperCase() || 'UNKNOWN');
|
||||||
|
|
||||||
await prisma.fileNode.upsert({
|
// 2. Call DAL to save to database
|
||||||
where: { oneDriveId: item.id }, // Primary match
|
await upsertFileNode(item.id, {
|
||||||
update: {
|
name: item.name,
|
||||||
name: item.name,
|
size: BigInt(item.size || 0),
|
||||||
size: BigInt(item.size || 0),
|
isFolder: isFolder,
|
||||||
isFolder: isFolder,
|
path: item.parentReference?.path + '/' + item.name,
|
||||||
path: item.parentReference?.path + '/' + item.name,
|
ownerId: session.user.id,
|
||||||
updatedAt: new Date(),
|
metadata: { type: extension, mimeType: item.file?.mimeType || null },
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
oneDriveId: item.id,
|
|
||||||
name: item.name,
|
|
||||||
size: BigInt(item.size || 0),
|
|
||||||
isFolder: isFolder,
|
|
||||||
path: item.parentReference?.path + '/' + item.name,
|
|
||||||
ownerId: session.user.id,
|
|
||||||
metadata: { type: extension, mimeType: item.file?.mimeType || null },
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
syncedCount++;
|
syncedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
revalidatePath('/dashboard');
|
revalidatePath('/dashboard');
|
||||||
return { success: true, count: syncedCount };
|
return { success: true, count: syncedCount };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error(error.message);
|
console.error("Sync Error:", error.message);
|
||||||
|
throw new Error("Failed to sync with OneDrive");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { getFreshAccessToken } from "@/lib/auth-utils";
|
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { createFileNode } from "@/data-access/file-nodes";
|
||||||
|
import { ensureOneDriveFolder, uploadLargeFile } from "@/services/onedrive";
|
||||||
|
|
||||||
export async function uploadFileAction(formData: FormData) {
|
export async function uploadFileAction(formData: FormData) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -11,58 +11,32 @@ export async function uploadFileAction(formData: FormData) {
|
||||||
|
|
||||||
const file = formData.get("file") as File;
|
const file = formData.get("file") as File;
|
||||||
const folderName = "WebCalibre";
|
const folderName = "WebCalibre";
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
|
||||||
|
|
||||||
// 1. Create/Check WebCalibre Folder
|
try {
|
||||||
const folderPath = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}`;
|
// 1. Logic: Ensure destination exists
|
||||||
const folderCheck = await fetch(folderPath, {
|
await ensureOneDriveFolder(session.user.id, folderName);
|
||||||
headers: { Authorization: `Bearer ${accessToken}` }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (folderCheck.status === 404) {
|
// 2. Logic: Perform the cloud upload
|
||||||
await fetch(`https://graph.microsoft.com/v1.0/me/drive/root/children`, {
|
const driveItem = await uploadLargeFile(session.user.id, file, folderName);
|
||||||
method: "POST",
|
|
||||||
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ name: folderName, folder: {} })
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Create Upload Session (Supports files > 4MB)
|
// 3. Logic: Save the result to our DB
|
||||||
const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}/${file.name}:/createUploadSession`;
|
await createFileNode({
|
||||||
const sessionRes = await fetch(sessionUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "rename" } })
|
|
||||||
});
|
|
||||||
|
|
||||||
const { uploadUrl } = await sessionRes.json();
|
|
||||||
|
|
||||||
// 3. Upload File Data
|
|
||||||
const buffer = Buffer.from(await file.arrayBuffer());
|
|
||||||
const uploadRes = await fetch(uploadUrl, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Length": `${file.size}`,
|
|
||||||
"Content-Range": `bytes 0-${file.size - 1}/${file.size}`
|
|
||||||
},
|
|
||||||
body: buffer
|
|
||||||
});
|
|
||||||
|
|
||||||
const driveItem = await uploadRes.json();
|
|
||||||
|
|
||||||
// 4. Record in PostgreSQL
|
|
||||||
await prisma.fileNode.create({
|
|
||||||
data: {
|
|
||||||
oneDriveId: driveItem.id,
|
oneDriveId: driveItem.id,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: BigInt(file.size),
|
size: BigInt(file.size),
|
||||||
isFolder: false,
|
isFolder: false,
|
||||||
path: `/${folderName}/${file.name}`,
|
path: `/${folderName}/${file.name}`,
|
||||||
ownerId: session.user.id,
|
ownerId: session.user.id,
|
||||||
metadata: { type: file.name.split('.').pop()?.toUpperCase() }
|
metadata: {
|
||||||
}
|
type: file.name.split('.').pop()?.toUpperCase(),
|
||||||
});
|
mimeType: file.type
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Upload Action Error:", error);
|
||||||
|
return { success: false, error: error.message || "Upload failed" };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button, Typography, Box, LinearProgress } from "@mui/material";
|
import { Button, Typography, Box, LinearProgress } from "@mui/material";
|
||||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||||
import { uploadFileToOneDrive } from "./upload-actions";
|
import { uploadFileAction } from "./upload-actions";
|
||||||
|
|
||||||
export default function UploadForm() {
|
export default function UploadForm() {
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
@ -14,7 +14,7 @@ export default function UploadForm() {
|
||||||
|
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
try {
|
try {
|
||||||
await uploadFileToOneDrive(formData);
|
await uploadFileAction(formData);
|
||||||
alert("Uploaded successfully!");
|
alert("Uploaded successfully!");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
|
// src/app/settings/actions.ts
|
||||||
|
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { getUserByEmail, getUserById, updateUserRole } from "@/data-access/users";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a user's role between 'ADMIN' and 'USER'.
|
* Toggles a user's role between 'ADMIN' and 'USER' using the DAL pattern.
|
||||||
* * Security Logic:
|
|
||||||
* 1. Checks if the caller is the Bootstrap Admin (via .env).
|
|
||||||
* 2. Checks if the caller has the 'ADMIN' role in the database.
|
|
||||||
* 3. Prevents the Bootstrap Admin from being demoted to 'USER'.
|
|
||||||
*/
|
*/
|
||||||
export async function toggleUserRoleAction(targetUserId: string) {
|
export async function toggleUserRoleAction(targetUserId: string) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -19,46 +17,34 @@ export async function toggleUserRoleAction(targetUserId: string) {
|
||||||
throw new Error("Unauthorized: No session found.");
|
throw new Error("Unauthorized: No session found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Authorization: Who is trying to change the role?
|
// 1. Authorization: Verify caller's permissions via DAL
|
||||||
const isBootstrap = callerEmail === process.env.INITIAL_ADMIN_EMAIL;
|
const isBootstrap = callerEmail === process.env.INITIAL_ADMIN_EMAIL;
|
||||||
|
const callerDbRecord = await getUserByEmail(callerEmail);
|
||||||
const callerDbRecord = await prisma.user.findUnique({
|
|
||||||
where: { email: callerEmail },
|
|
||||||
select: { role: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
const isAdmin = isBootstrap || callerDbRecord?.role === "ADMIN";
|
const isAdmin = isBootstrap || callerDbRecord?.role === "ADMIN";
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
throw new Error("Forbidden: You do not have permission to manage roles.");
|
throw new Error("Forbidden: You do not have permission to manage roles.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fetch the target user to be modified
|
// 2. Fetch target user via DAL
|
||||||
const targetUser = await prisma.user.findUnique({
|
const targetUser = await getUserById(targetUserId);
|
||||||
where: { id: targetUserId },
|
|
||||||
select: { id: true, email: true, role: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!targetUser) {
|
if (!targetUser) {
|
||||||
throw new Error("User not found.");
|
throw new Error("User not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Protection: Prevent demoting the primary bootstrap admin
|
// 3. Protection: Prevent demoting the primary bootstrap admin
|
||||||
// This ensures you don't accidentally lock yourself out of the settings page.
|
|
||||||
if (targetUser.email === process.env.INITIAL_ADMIN_EMAIL && targetUser.role === "ADMIN") {
|
if (targetUser.email === process.env.INITIAL_ADMIN_EMAIL && targetUser.role === "ADMIN") {
|
||||||
throw new Error("Security Restriction: The primary Bootstrap Admin role cannot be removed.");
|
throw new Error("Security Restriction: The primary Bootstrap Admin role cannot be removed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Determine new role
|
// 4. Logic: Determine new role
|
||||||
const newRole = targetUser.role === "ADMIN" ? "USER" : "ADMIN";
|
const newRole = targetUser.role === "ADMIN" ? "USER" : "ADMIN";
|
||||||
|
|
||||||
// 5. Execute Update
|
// 5. Execute Update via DAL
|
||||||
await prisma.user.update({
|
await updateUserRole(targetUserId, newRole);
|
||||||
where: { id: targetUserId },
|
|
||||||
data: { role: newRole }
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. Refresh the data on the Settings page
|
// 6. UI Invalidation
|
||||||
revalidatePath("/settings");
|
revalidatePath("/settings");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
//src/app/update/[id]/_actions.ts
|
// src/app/update/[id]/_actions.ts
|
||||||
|
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { getFileNodeById, updateFileNode } from "@/data-access/file-nodes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVER ACTION: Updates file metadata and organizational data.
|
||||||
|
* This refactored version uses the Data Access Layer (DAL) to
|
||||||
|
* ensure separation of concerns.
|
||||||
|
*/
|
||||||
export async function updateFileAction(formData: FormData) {
|
export async function updateFileAction(formData: FormData) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
||||||
|
// 1. Authorization Guard
|
||||||
if (!session?.user?.id) throw new Error("Unauthorized");
|
if (!session?.user?.id) throw new Error("Unauthorized");
|
||||||
|
|
||||||
|
// 2. Data Extraction
|
||||||
const id = formData.get("id") as string;
|
const id = formData.get("id") as string;
|
||||||
const name = formData.get("name") as string;
|
const name = formData.get("name") as string;
|
||||||
const description = formData.get("description") as string;
|
const description = formData.get("description") as string;
|
||||||
|
|
@ -20,30 +28,39 @@ export async function updateFileAction(formData: FormData) {
|
||||||
const customMetadata = customMetadataRaw ? JSON.parse(customMetadataRaw) : {};
|
const customMetadata = customMetadataRaw ? JSON.parse(customMetadataRaw) : {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Get existing record to preserve system metadata (like mimeType)
|
// 3. DAL: Fetch existing record to safely merge metadata
|
||||||
const existing = await prisma.fileNode.findUnique({ where: { id } });
|
// This replaces the direct prisma.fileNode.findUnique call
|
||||||
const existingMetadata = (existing?.metadata as Record<string, any>) || {};
|
const existing = await getFileNodeById(id);
|
||||||
|
if (!existing) throw new Error("File record not found");
|
||||||
|
|
||||||
// 2. Update the record
|
const existingMetadata = (existing.metadata as Record<string, any>) || {};
|
||||||
await prisma.fileNode.update({
|
|
||||||
where: { id },
|
// 4. Logic: Prepare the updated metadata object
|
||||||
data: {
|
const updatedMetadata = {
|
||||||
name,
|
...customMetadata, // Apply new user keys
|
||||||
description,
|
type: name.split('.').pop()?.toUpperCase() || existingMetadata.type || "FILE",
|
||||||
parentId,
|
mimeType: existingMetadata.mimeType // Ensure system metadata isn't overwritten
|
||||||
metadata: {
|
};
|
||||||
...customMetadata, // User's new keys
|
|
||||||
type: name.split('.').pop()?.toUpperCase() || existingMetadata.type || "FILE",
|
// 5. DAL: Perform the update
|
||||||
mimeType: existingMetadata.mimeType // Preserve the original mimeType
|
// This replaces the direct prisma.fileNode.update call
|
||||||
}
|
await updateFileNode(id, {
|
||||||
}
|
name,
|
||||||
|
description,
|
||||||
|
parentId,
|
||||||
|
metadata: updatedMetadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 6. Cache Invalidation
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
revalidatePath(`/update/${id}`);
|
revalidatePath(`/update/${id}`);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Update error:", error);
|
console.error("Update action error:", error);
|
||||||
return { success: false, message: error.message };
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.message || "An unexpected error occurred during update"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +116,7 @@ export default function UpdateView({ fileNode, folders }: any) {
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
{customMetadata.map((row, index) => (
|
{customMetadata.map((row, index) => (
|
||||||
<Grid container spacing={1} key={index} alignItems="center">
|
<Grid container spacing={1} key={index} alignItems="center">
|
||||||
<Grid item xs={5}>
|
<Grid size={ {xs:5}}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth size="small" placeholder="Key"
|
fullWidth size="small" placeholder="Key"
|
||||||
value={row.key}
|
value={row.key}
|
||||||
|
|
@ -127,7 +127,7 @@ export default function UpdateView({ fileNode, folders }: any) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6}>
|
<Grid size={{xs:6}}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth size="small" placeholder="Value"
|
fullWidth size="small" placeholder="Value"
|
||||||
value={row.value}
|
value={row.value}
|
||||||
|
|
@ -138,7 +138,7 @@ export default function UpdateView({ fileNode, folders }: any) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={1}>
|
<Grid size={{xs:1}}>
|
||||||
<IconButton color="error" onClick={() => setCustomMetadata(customMetadata.filter((_, i) => i !== index))}>
|
<IconButton color="error" onClick={() => setCustomMetadata(customMetadata.filter((_, i) => i !== index))}>
|
||||||
<DeleteOutlineIcon />
|
<DeleteOutlineIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
|
// src/app/upload/_actions.ts
|
||||||
|
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { getFreshAccessToken } from "@/lib/auth-utils";
|
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { createFileNode } from "@/data-access/file-nodes";
|
||||||
|
//import { createOneDriveFolder, uploadToFolderId, ensureOneDriveFolder } from "@/services/onedrive";
|
||||||
|
|
||||||
|
import { createOneDriveFolder, uploadToFolderId, ensureOneDriveFolder } from "@/services/onedrive";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. CREATE FOLDER: Virtual Only
|
* 1. CREATE VIRTUAL FOLDER
|
||||||
* Logic: User-created organizational folders exist ONLY in the database.
|
|
||||||
* No call to OneDrive is made here.
|
|
||||||
*/
|
*/
|
||||||
export async function createFolderAction(name: string, parentId?: string | null) {
|
export async function createFolderAction(name: string, parentId?: string | null) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -16,34 +18,26 @@ export async function createFolderAction(name: string, parentId?: string | null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const internalId = crypto.randomUUID();
|
const internalId = crypto.randomUUID();
|
||||||
|
// Swapped createNode for createFileNode
|
||||||
const newNode = await prisma.fileNode.create({
|
const newNode = await createFileNode({
|
||||||
data: {
|
id: internalId,
|
||||||
id: internalId,
|
oneDriveId: null,
|
||||||
oneDriveId: null, // Virtual folders do not have a cloud ID
|
name,
|
||||||
name: name,
|
isFolder: true,
|
||||||
isFolder: true,
|
path: `virtual:/${name}`,
|
||||||
path: `virtual:/${name}`,
|
ownerId: session.user.id,
|
||||||
ownerId: session.user.id,
|
parentId: parentId || null,
|
||||||
parentId: parentId || null,
|
metadata: { type: "FOLDER" }
|
||||||
metadata: { type: "FOLDER" }
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath("/upload");
|
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
|
|
||||||
return { success: true, node: newNode };
|
return { success: true, node: newNode };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Folder creation error:", error);
|
|
||||||
throw new Error(error.message || "Failed to create virtual folder");
|
throw new Error(error.message || "Failed to create virtual folder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2. UPLOAD FILE: Physical Container
|
* 2. UPLOAD FILE (Physical UUID Folder)
|
||||||
* Logic: Creates a physical folder (UUID) on OneDrive to hold the file.
|
|
||||||
* This ensures every file has a unique storage space in the cloud.
|
|
||||||
*/
|
*/
|
||||||
export async function uploadFileAction(formData: FormData) {
|
export async function uploadFileAction(formData: FormData) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -59,77 +53,48 @@ export async function uploadFileAction(formData: FormData) {
|
||||||
|
|
||||||
if (!file) throw new Error("No file selected");
|
if (!file) throw new Error("No file selected");
|
||||||
|
|
||||||
const accessToken = await getFreshAccessToken(session.user.id);
|
|
||||||
const rootFolder = "WebCalibre";
|
const rootFolder = "WebCalibre";
|
||||||
const internalId = crypto.randomUUID(); // This UUID will be the OneDrive folder name
|
const internalId = crypto.randomUUID(); // Used for both DB ID and OneDrive Folder Name
|
||||||
|
|
||||||
// 1. Create the Physical Storage Folder on OneDrive
|
try {
|
||||||
const createSubFolderRes = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:/${rootFolder}:/children`, {
|
// A. Ensure root exists
|
||||||
method: "POST",
|
await ensureOneDriveFolder(session.user.id, rootFolder);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: internalId,
|
|
||||||
folder: {},
|
|
||||||
"@microsoft.graph.conflictBehavior": "fail"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!createSubFolderRes.ok) {
|
// B. Create the physical UUID folder on OneDrive
|
||||||
const errorData = await createSubFolderRes.json();
|
const subFolderRes = await createOneDriveFolder(session.user.id, rootFolder, internalId);
|
||||||
throw new Error(errorData.error?.message || "Storage directory creation failed");
|
const subFolderData = await subFolderRes.json();
|
||||||
|
|
||||||
|
// C. Upload the file binary into that specific folder
|
||||||
|
const uploadedFileData = await uploadToFolderId(session.user.id, file, subFolderData.id);
|
||||||
|
|
||||||
|
// D. Create record in Database
|
||||||
|
// src/app/upload/_actions.ts
|
||||||
|
|
||||||
|
// ... inside uploadFileAction or createFolderAction ...
|
||||||
|
// ... inside uploadFileAction after OneDrive work is done ...
|
||||||
|
|
||||||
|
await createFileNode({
|
||||||
|
id: internalId,
|
||||||
|
oneDriveId: uploadedFileData.id,
|
||||||
|
name: file.name,
|
||||||
|
description: description,
|
||||||
|
size: BigInt(file.size),
|
||||||
|
isFolder: false,
|
||||||
|
path: `/${rootFolder}/${internalId}/${file.name}`,
|
||||||
|
ownerId: session.user.id,
|
||||||
|
parentId: parentId,
|
||||||
|
metadata: {
|
||||||
|
...customMetadata, // User's custom keys from the form
|
||||||
|
type: file.name.split('.').pop()?.toUpperCase() || "UNKNOWN",
|
||||||
|
mimeType: file.type
|
||||||
}
|
}
|
||||||
const subFolderData = await createSubFolderRes.json();
|
});
|
||||||
|
|
||||||
// 2. Create Upload Session inside the new Physical Folder
|
revalidatePath("/dashboard");
|
||||||
const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${subFolderData.id}:/${encodeURIComponent(file.name)}:/createUploadSession`;
|
revalidatePath("/upload");
|
||||||
const sessionRes = await fetch(sessionUrl, {
|
return { success: true };
|
||||||
method: "POST",
|
} catch (error: any) {
|
||||||
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
console.error("Upload refactor error:", error);
|
||||||
body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "replace" } })
|
return { success: false, error: error.message };
|
||||||
});
|
}
|
||||||
|
|
||||||
const { uploadUrl } = await sessionRes.json();
|
|
||||||
const buffer = Buffer.from(await file.arrayBuffer());
|
|
||||||
|
|
||||||
// 3. PUT the file binary
|
|
||||||
const uploadRes = await fetch(uploadUrl, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Length": `${file.size}`,
|
|
||||||
"Content-Range": `bytes 0-${file.size - 1}/${file.size}`
|
|
||||||
},
|
|
||||||
body: buffer
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploadedFileData = await uploadRes.json();
|
|
||||||
const oneDriveId = uploadedFileData.id;
|
|
||||||
const extension = file.name.split('.').pop()?.toUpperCase() || "UNKNOWN";
|
|
||||||
|
|
||||||
// 4. Create record in Database
|
|
||||||
// Link it to the VIRTUAL folder via parentId
|
|
||||||
await prisma.fileNode.create({
|
|
||||||
data: {
|
|
||||||
id: internalId,
|
|
||||||
oneDriveId: oneDriveId,
|
|
||||||
name: file.name,
|
|
||||||
description: description,
|
|
||||||
size: BigInt(file.size),
|
|
||||||
isFolder: false,
|
|
||||||
path: `/${rootFolder}/${internalId}/${file.name}`,
|
|
||||||
ownerId: session.user.id,
|
|
||||||
parentId: parentId,
|
|
||||||
metadata: {
|
|
||||||
...customMetadata,
|
|
||||||
type: extension,
|
|
||||||
mimeType: file.type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
revalidatePath("/dashboard");
|
|
||||||
revalidatePath("/upload");
|
|
||||||
return { success: true };
|
|
||||||
}
|
}
|
||||||
|
|
@ -208,19 +208,19 @@ export default function UploadView({ user, folders = [] }: any) {
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
{customMetadata.map((row, index) => (
|
{customMetadata.map((row, index) => (
|
||||||
<Grid container spacing={1} key={index} alignItems="center">
|
<Grid container spacing={1} key={index} alignItems="center">
|
||||||
<Grid item xs={5}>
|
<Grid size={{xs:5}}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth size="small" placeholder="Key (e.g. Project-ID)"
|
fullWidth size="small" placeholder="Key (e.g. Project-ID)"
|
||||||
value={row.key} onChange={(e) => updateMetadataRow(index, 'key', e.target.value)}
|
value={row.key} onChange={(e) => updateMetadataRow(index, 'key', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6}>
|
<Grid size= {{xs:6}}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth size="small" placeholder="Value"
|
fullWidth size="small" placeholder="Value"
|
||||||
value={row.value} onChange={(e) => updateMetadataRow(index, 'value', e.target.value)}
|
value={row.value} onChange={(e) => updateMetadataRow(index, 'value', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={1}>
|
<Grid size= {{xs:1}}>
|
||||||
<IconButton color="error" onClick={() => removeMetadataRow(index)}>
|
<IconButton color="error" onClick={() => removeMetadataRow(index)}>
|
||||||
<DeleteOutlineIcon />
|
<DeleteOutlineIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
||||||
18
src/components/LogoutButton.tsx
Normal file
18
src/components/LogoutButton.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { signOut } from "next-auth/react";
|
||||||
|
import { Button } from "@mui/material";
|
||||||
|
import LogoutIcon from '@mui/icons-material/Logout';
|
||||||
|
|
||||||
|
export default function LogoutButton() {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
startIcon={<LogoutIcon />}
|
||||||
|
onClick={() => signOut({ callbackUrl: "/" })}
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
// ... other imports like ThemeProvider
|
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
|
||||||
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
import theme from '@/lib/theme'; // Import your custom theme here
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<AppRouterCacheProvider>
|
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
{/* CssBaseline resets browser styles to match MUI and your theme */}
|
||||||
|
<CssBaseline />
|
||||||
{children}
|
{children}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AppRouterCacheProvider>
|
</AppRouterCacheProvider>
|
||||||
|
|
|
||||||
104
src/data-access/file-nodes.ts
Normal file
104
src/data-access/file-nodes.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
// src/data-access/file-nodes.ts
|
||||||
|
import "server-only";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FETCH: Retrieve all nodes for the dashboard.
|
||||||
|
* Centralizing this here allows us to change sort order or filters
|
||||||
|
* in one place for the entire application.
|
||||||
|
*/
|
||||||
|
export async function getAllFileNodes() {
|
||||||
|
return await prisma.fileNode.findMany({
|
||||||
|
orderBy: {
|
||||||
|
updatedAt: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FETCH: Get a single node by ID.
|
||||||
|
* Used by the Download route and Update pages to verify a file exists.
|
||||||
|
*/
|
||||||
|
export async function getFileNodeById(id: string) {
|
||||||
|
return await prisma.fileNode.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPDATE: Modify metadata, name, or virtual location.
|
||||||
|
* This function accepts the data object to keep the DAL flexible.
|
||||||
|
*/
|
||||||
|
export async function updateFileNode(id: string, data: any) {
|
||||||
|
return await prisma.fileNode.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE: Remove the record from the database.
|
||||||
|
* Cloud deletion should be handled by the Service Layer before calling this.
|
||||||
|
*/
|
||||||
|
export async function deleteFileNode(id: string) {
|
||||||
|
return await prisma.fileNode.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MASTER CREATE: Handles both standard uploads and virtual folders.
|
||||||
|
* If no ID is provided, it generates a fresh UUID.
|
||||||
|
*/
|
||||||
|
export async function createFileNode(data: {
|
||||||
|
id?: string; // Optional: used for virtual folders/UUID storage
|
||||||
|
oneDriveId: string | null;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
isFolder: boolean;
|
||||||
|
path: string;
|
||||||
|
ownerId: string;
|
||||||
|
parentId?: string | null; // Optional: for nested structures
|
||||||
|
size?: bigint;
|
||||||
|
metadata: any;
|
||||||
|
}) {
|
||||||
|
return await prisma.fileNode.create({
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
id: data.id ?? crypto.randomUUID(), // Use provided ID or generate new one
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ... other functions (getAllFileNodes, etc)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPSERT: Create or Update a file node based on OneDrive ID
|
||||||
|
* Moved here because it interacts with the Database.
|
||||||
|
*/
|
||||||
|
export async function upsertFileNode(oneDriveId: string, data: any) {
|
||||||
|
return await prisma.fileNode.upsert({
|
||||||
|
where: { oneDriveId },
|
||||||
|
update: {
|
||||||
|
name: data.name,
|
||||||
|
size: data.size,
|
||||||
|
isFolder: data.isFolder,
|
||||||
|
path: data.path,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
oneDriveId: oneDriveId,
|
||||||
|
name: data.name,
|
||||||
|
size: data.size,
|
||||||
|
isFolder: data.isFolder,
|
||||||
|
path: data.path,
|
||||||
|
ownerId: data.ownerId,
|
||||||
|
metadata: data.metadata,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
42
src/data-access/users.ts
Normal file
42
src/data-access/users.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import "server-only";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FETCH: Get user by Email
|
||||||
|
* Used for authorization checks in actions.
|
||||||
|
*/
|
||||||
|
export async function getUserByEmail(email: string) {
|
||||||
|
return await prisma.user.findUnique({
|
||||||
|
where: { email },
|
||||||
|
select: { id: true, email: true, role: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FETCH: Get user by ID
|
||||||
|
*/
|
||||||
|
export async function getUserById(id: string) {
|
||||||
|
return await prisma.user.findUnique({
|
||||||
|
where: { id },
|
||||||
|
select: { id: true, email: true, role: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPDATE: Update user role
|
||||||
|
*/
|
||||||
|
export async function updateUserRole(id: string, role: "ADMIN" | "USER") {
|
||||||
|
return await prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data: { role }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FETCH: List all users (for the settings table)
|
||||||
|
*/
|
||||||
|
export async function getAllUsers() {
|
||||||
|
return await prisma.user.findMany({
|
||||||
|
orderBy: { email: 'asc' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { createTheme } from '@mui/material/styles';
|
import { createTheme } from '@mui/material/styles';
|
||||||
import { Roboto } from 'next/font/google';
|
import { Roboto } from 'next/font/google';
|
||||||
|
|
||||||
// Load the font optimized for Next.js
|
|
||||||
const roboto = Roboto({
|
const roboto = Roboto({
|
||||||
weight: ['300', '400', '500', '700'],
|
weight: ['300', '400', '500', '700'],
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
|
|
@ -9,39 +8,36 @@ const roboto = Roboto({
|
||||||
});
|
});
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
// 1. Color Palette (Clean & Professional for a Library App)
|
|
||||||
palette: {
|
palette: {
|
||||||
mode: 'light',
|
mode: 'light',
|
||||||
primary: {
|
primary: {
|
||||||
main: '#1976d2', // Professional Blue
|
main: '#1976d2',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: '#9c27b0', // Purple for accents
|
main: '#9c27b0',
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
default: '#f4f6f8', // Light grey for the app background
|
default: '#f4f6f8',
|
||||||
paper: '#ffffff',
|
paper: '#ffffff',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// 2. Typography
|
|
||||||
typography: {
|
typography: {
|
||||||
fontFamily: roboto.style.fontFamily,
|
fontFamily: roboto.style.fontFamily,
|
||||||
h6: {
|
h6: {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// 3. Component Defaults
|
|
||||||
components: {
|
components: {
|
||||||
|
// Keep your button styles
|
||||||
MuiButton: {
|
MuiButton: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
textTransform: 'none', // Prevents all-caps buttons
|
textTransform: 'none',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Keep your paper styles
|
||||||
MuiPaper: {
|
MuiPaper: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
|
|
@ -52,6 +48,12 @@ const theme = createTheme({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// ADD THIS: Ensures the theme is compatible with MUI v7's Grid logic
|
||||||
|
MuiStack: {
|
||||||
|
defaultProps: {
|
||||||
|
useFlexGap: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
172
src/services/onedrive.ts
Normal file
172
src/services/onedrive.ts
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
// src/services/onedrive.ts
|
||||||
|
import "server-only";
|
||||||
|
import { getFreshAccessToken } from "@/lib/auth-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PRIVATE HELPER: graphRequest
|
||||||
|
* This internal function handles the heavy lifting of fetching tokens
|
||||||
|
* and making the actual HTTP call to Microsoft.
|
||||||
|
*/
|
||||||
|
async function graphRequest(userId: string, endpoint: string, options: RequestInit = {}) {
|
||||||
|
// 1. Automatically handle token refresh logic
|
||||||
|
const token = await getFreshAccessToken(userId);
|
||||||
|
const baseUrl = "https://graph.microsoft.com/v1.0";
|
||||||
|
|
||||||
|
const res = await fetch(`${baseUrl}${endpoint}`, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
...options.headers,
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Centralized Error Handling for OneDrive
|
||||||
|
if (!res.ok) {
|
||||||
|
const errorData = await res.text();
|
||||||
|
console.error(`OneDrive API Error [${endpoint}]:`, errorData);
|
||||||
|
throw new Error(`OneDrive API failed: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: Download File Content
|
||||||
|
* Returns the raw binary stream from OneDrive.
|
||||||
|
*/
|
||||||
|
export async function getOneDriveContentStream(userId: string, oneDriveId: string) {
|
||||||
|
return await graphRequest(userId, `/me/drive/items/${oneDriveId}/content`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: Get File Metadata
|
||||||
|
* Used to get the @microsoft.graph.downloadUrl or driveItem properties.
|
||||||
|
*/
|
||||||
|
export async function getOneDriveItem(userId: string, oneDriveId: string) {
|
||||||
|
const res = await graphRequest(userId, `/me/drive/items/${oneDriveId}`);
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: Upload File
|
||||||
|
* Handles the PUT request to OneDrive for new or updated files.
|
||||||
|
*/
|
||||||
|
export async function uploadToOneDrive(userId: string, file: File, oneDriveId?: string) {
|
||||||
|
// If oneDriveId exists, we update. Otherwise, we'd use a path (needs expansion for new files).
|
||||||
|
const endpoint = oneDriveId
|
||||||
|
? `/me/drive/items/${oneDriveId}/content`
|
||||||
|
: `/me/drive/root:/${file.name}:/content`;
|
||||||
|
|
||||||
|
return await graphRequest(userId, endpoint, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": file.type },
|
||||||
|
body: Buffer.from(await file.arrayBuffer()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: Delete from Cloud
|
||||||
|
*/
|
||||||
|
export async function deleteFromOneDrive(userId: string, oneDriveId: string) {
|
||||||
|
return await graphRequest(userId, `/me/drive/items/${oneDriveId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: List Children of the WebCalibre folder
|
||||||
|
*/
|
||||||
|
export async function getWebCalibreChildren(userId: string) {
|
||||||
|
const res = await graphRequest(userId, "/me/drive/root:/WebCalibre:/children");
|
||||||
|
const data = await res.json();
|
||||||
|
return data.value; // Returns the array of driveItems
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: Ensure a specific folder exists in OneDrive
|
||||||
|
* Returns the folder ID
|
||||||
|
*/
|
||||||
|
export async function ensureOneDriveFolder(userId: string, folderName: string) {
|
||||||
|
try {
|
||||||
|
const res = await graphRequest(userId, `/me/drive/root:/${folderName}`);
|
||||||
|
const data = await res.json();
|
||||||
|
return data.id;
|
||||||
|
} catch (error) {
|
||||||
|
// If 404, create it
|
||||||
|
const createRes = await graphRequest(userId, `/me/drive/root/children`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ name: folderName, folder: {} })
|
||||||
|
});
|
||||||
|
const data = await createRes.json();
|
||||||
|
return data.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: Upload Large File via Session
|
||||||
|
* This replaces the basic PUT for better reliability
|
||||||
|
*/
|
||||||
|
export async function uploadLargeFile(userId: string, file: File, folderName: string) {
|
||||||
|
// 1. Create Upload Session
|
||||||
|
const sessionRes = await graphRequest(userId, `/me/drive/root:/${folderName}/${file.name}:/createUploadSession`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "rename" } })
|
||||||
|
});
|
||||||
|
const { uploadUrl } = await sessionRes.json();
|
||||||
|
|
||||||
|
// 2. Upload the data to the provided URL (No Authorization header needed for the uploadUrl itself)
|
||||||
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
const uploadRes = await fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Length": `${file.size}`,
|
||||||
|
"Content-Range": `bytes 0-${file.size - 1}/${file.size}`
|
||||||
|
},
|
||||||
|
body: buffer
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!uploadRes.ok) throw new Error("Upload session failed");
|
||||||
|
return await uploadRes.json(); // Returns the DriveItem
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* SERVICE: Create a folder by name inside a parent path
|
||||||
|
*/
|
||||||
|
export async function createOneDriveFolder(userId: string, parentPath: string, folderName: string) {
|
||||||
|
return await graphRequest(userId, `/me/drive/root:/${parentPath}:/children`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: folderName,
|
||||||
|
folder: {},
|
||||||
|
"@microsoft.graph.conflictBehavior": "fail"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVICE: Upload to a specific folder ID (using session)
|
||||||
|
*/
|
||||||
|
export async function uploadToFolderId(userId: string, file: File, folderId: string) {
|
||||||
|
const sessionRes = await graphRequest(userId, `/me/drive/items/${folderId}:/${encodeURIComponent(file.name)}:/createUploadSession`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "replace" } })
|
||||||
|
});
|
||||||
|
|
||||||
|
const { uploadUrl } = await sessionRes.json();
|
||||||
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
|
||||||
|
const uploadRes = await fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Length": `${file.size}`,
|
||||||
|
"Content-Range": `bytes 0-${file.size - 1}/${file.size}`
|
||||||
|
},
|
||||||
|
body: buffer
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!uploadRes.ok) throw new Error("Upload failed");
|
||||||
|
return await uploadRes.json();
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue