diff --git a/.env b/.env index 245da9e..667e6ce 100644 --- a/.env +++ b/.env @@ -1,6 +1,17 @@ -DATABASE_URL="postgresql://stephen:Web2025$$@192.168.1.210:5432/webcalibre2" -# Credentials for your Microsoft App (NextAuth) -AZURE_AD_CLIENT_ID="549931a-f491-436b-b7bc-d37d8ca3c17e" -AZURE_AD_CLIENT_SECRET="6a242be1-c711-4cc3-a132-03c1f57993ed" -AZURE_AD_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0" \ No newline at end of file +DATABASE_URL="postgresql://stephen:Web2025%24%24@192.168.1.210:5432/webcalibre2" +# values generated by Gemini +# Generated for security +AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" + +# Use 'common' for multi-tenant (Work + Personal) support +AUTH_MICROSOFT_ENTRA_ID_TENANT_ID="common" + +# The Application (client) ID +AUTH_MICROSOFT_ENTRA_ID_ID="b549931a-f491-436b-b7bc-d37d8ca3c17e" + +# The Client Secret VALUE (ensure no leading '6' or spaces) +AUTH_MICROSOFT_ENTRA_ID_SECRET="O3p8Q~oMph-0kSLwkvzEJzJdx_iHGOjJjrr5Pa1A" + +# Required to tell Auth.js to trust your localhost/proxy URL +AUTH_TRUST_HOST=true \ No newline at end of file diff --git a/.env-bak b/.env-bak new file mode 100644 index 0000000..245da9e --- /dev/null +++ b/.env-bak @@ -0,0 +1,6 @@ +DATABASE_URL="postgresql://stephen:Web2025$$@192.168.1.210:5432/webcalibre2" + +# Credentials for your Microsoft App (NextAuth) +AZURE_AD_CLIENT_ID="549931a-f491-436b-b7bc-d37d8ca3c17e" +AZURE_AD_CLIENT_SECRET="6a242be1-c711-4cc3-a132-03c1f57993ed" +AZURE_AD_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0" \ No newline at end of file diff --git a/.env.local b/.env.local index 2701d8d..667e6ce 100644 --- a/.env.local +++ b/.env.local @@ -1,4 +1,5 @@ +DATABASE_URL="postgresql://stephen:Web2025%24%24@192.168.1.210:5432/webcalibre2" # values generated by Gemini # Generated for security AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" diff --git a/.gitignore b/.gitignore index 8c5b2ee..d4c8708 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +/src/generated/prisma diff --git a/docs/notes.md b/docs/notes.md index 38fd985..8409b5c 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -4,6 +4,7 @@ - [1.3. Overview of user consent and how to manage it in Microsoft Entra | Microsoft](#13-overview-of-user-consent-and-how-to-manage-it-in-microsoft-entra--microsoft) - [1.4. Add Tailwind CSS to an Existing Next js Project](#14-add-tailwind-css-to-an-existing-next-js-project) - [1.5. How to style Material UI component with Tailwindcss in React project](#15-how-to-style-material-ui-component-with-tailwindcss-in-react-project) + - [1.6. How to Use Prisma 7 in Next.js](#16-how-to-use-prisma-7-in-nextjs) - [2. Project Structure](#2-project-structure) - [3. App Registrations](#3-app-registrations) - [3.1. Permissions Needed](#31-permissions-needed) @@ -14,6 +15,15 @@ - [4.4. Test nextjs](#44-test-nextjs) - [4.5. Check Authentication](#45-check-authentication) - [4.6. Install NextAuth.js v5](#46-install-nextauthjs-v5) +- [5. .env files](#5-env-files) + - [5.1. .env.local](#51-envlocal) + - [5.2. .env](#52-env) +- [6. Prisma Install](#6-prisma-install) + - [6.1. The CLI is for development tasks like migrations](#61-the-cli-is-for-development-tasks-like-migrations) + - [6.2. Create the prisma folder](#62-create-the-prisma-folder) +- [7. Run this whenever you change your schema.prisma file](#7-run-this-whenever-you-change-your-schemaprisma-file) + - [7.1. Pro-Tip: Update your package.json](#71-pro-tip-update-your-packagejson) +- [8. Where we are up to 8/1/2026](#8-where-we-are-up-to-812026) # 1. Reference @@ -37,6 +47,11 @@ [How to style Material UI component with Tailwindcss in React project](https://www.youtube.com/watch?v=QQIfuMlA6TI) + +## 1.6. How to Use Prisma 7 in Next.js + +[How to Use Prisma 7 in Next.js](https://www.youtube.com/watch?v=Ndhx_rNkoUw) + # 2. Project Structure The following is the desire structure @@ -229,4 +244,100 @@ created .env.local ```text AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" # Added by `npx auth`. Read more: https://cli.authjs.dev -``` \ No newline at end of file +``` + +# 5. .env files + +## 5.1. .env.local +```text +# values generated by Gemini +# Generated for security +AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" + +# Use 'common' for multi-tenant (Work + Personal) support +AUTH_MICROSOFT_ENTRA_ID_TENANT_ID="common" + +# The Application (client) ID +AUTH_MICROSOFT_ENTRA_ID_ID="b549931a-f491-436b-b7bc-d37d8ca3c17e" + +# The Client Secret VALUE (ensure no leading '6' or spaces) +AUTH_MICROSOFT_ENTRA_ID_SECRET="O3p8Q~oMph-0kSLwkvzEJzJdx_iHGOjJjrr5Pa1A" + +# Required to tell Auth.js to trust your localhost/proxy URL +AUTH_TRUST_HOST=true +``` + +## 5.2. .env +``` +DATABASE_URL="postgresql://stephen:Web2025$$@192.168.1.210:5432/webcalibre2" + +# Credentials for your Microsoft App (NextAuth) +AZURE_AD_CLIENT_ID="549931a-f491-436b-b7bc-d37d8ca3c17e" +AZURE_AD_CLIENT_SECRET="6a242be1-c711-4cc3-a132-03c1f57993ed" +AZURE_AD_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0" +``` + +# 6. Prisma Install + +To get Prisma fully installed and ready to talk to your PostgreSQL database, you need to install the CLI (for migrations) and the Client (for your code to query the database). + +Run these three commands in your terminal: + +1. Install the Prisma packages +Bash + +## 6.1. The CLI is for development tasks like migrations +npm install prisma --save-dev + +##The Client is used by your Next.js code to talk to the DB +```zsh +npm install @prisma/client +``` +2. Install the Dotenv helper +Since you are keeping your DATABASE_URL in .env.local (and your password has special characters), you need this to bridge the gap between Next.js and the Prisma CLI: + +```zsh + +npm install dotenv-cli --save-dev +``` +3. Initialize and Generate +Once the packages are installed, you need to initialize the Prisma folder (if you haven't already) and generate the types for your FileNode model: + +Bash + +## 6.2. Create the prisma folder +```zsh +npx prisma init +``` +# 7. Run this whenever you change your schema.prisma file +npx dotenv -e .env.local -- npx prisma generate +4. Apply your Schema to PostgreSQL +Finally, run this to actually create the tables in your webcalibre2 database: + + + +npx dotenv -e .env.local -- npx prisma migrate dev --name init_database + +## 7.1. Pro-Tip: Update your package.json +To avoid typing that long dotenv command every time, open your package.json and add this to the "scripts" section: + +JSON + +"scripts": { + "db:migrate": "dotenv -e .env.local -- prisma migrate dev", + "db:studio": "dotenv -e .env.local -- prisma studio" +} +Now, you can just run npm run db:migrate whenever you update your schema! + +# 8. Where we are up to 8/1/2026 + +We can authenticate with MS Azure, upload a file and store it on my personal OneDrive. +I can see a few problems, + +1. if the file gets stored with its original filename, if I store the same file again it tells that it fail, but in reality it did not fail the it got renamed with 1.pdf added this is not added data base. + +2. We may be better to create a folder and put the file in the folder, to me the logical name would id of the file, but that is automatically create and assigned when the data is stored as a UUID. + +3. We would have to create the UUID ourselves, so that we can create a folder with that specific UUID and store the file in the folder. + +4. The Upload file Page needs to be able to create folder as well select a parent , add a description of either the file \ No newline at end of file diff --git a/docs/notes.pdf b/docs/notes.pdf index fbb81ea..3b6ec2c 100644 Binary files a/docs/notes.pdf and b/docs/notes.pdf differ diff --git a/package-lock.json b/package-lock.json index 41e6ecb..f7dadb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,31 @@ "name": "124_webcalibre2", "version": "0.1.0", "dependencies": { + "@auth/prisma-adapter": "^2.11.1", "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.6", "@mui/material": "^7.3.6", "@mui/material-nextjs": "^7.3.6", + "@mui/x-data-grid": "^8.23.0", + "@prisma/adapter-pg": "^7.2.0", + "@prisma/client": "^7.2.0", "next": "16.1.1", "next-auth": "^5.0.0-beta.30", + "pg": "^8.16.3", "react": "19.2.3", "react-dom": "19.2.3" }, "devDependencies": { "@types/node": "^20", + "@types/pg": "^8.16.0", "@types/react": "^19", "@types/react-dom": "^19", + "dotenv-cli": "^11.0.0", "eslint": "^9", "eslint-config-next": "16.1.1", + "prisma": "^7.2.0", "typescript": "^5" } }, @@ -57,6 +65,47 @@ } } }, + "node_modules/@auth/prisma-adapter": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.1.tgz", + "integrity": "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.41.1" + }, + "peerDependencies": { + "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" + } + }, + "node_modules/@auth/prisma-adapter/node_modules/@auth/core": { + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.1.tgz", + "integrity": "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==", + "license": "ISC", + "dependencies": { + "@panva/hkdf": "^1.2.1", + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^7.0.7" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -296,6 +345,73 @@ "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": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.0.tgz", @@ -625,6 +741,19 @@ "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": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1189,6 +1318,20 @@ "@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": { "version": "7.3.6", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.6.tgz", @@ -1469,6 +1612,88 @@ "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", "license": "MIT" }, + "node_modules/@mui/x-data-grid": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.23.0.tgz", + "integrity": "sha512-fv8AiibTgwPKJsXtYdc5bYDDW3EQp/ieQ0iUejvZ2JbYikMIPTYjI4pHy7zW3F3RDsi6DGQnw5AW6LeoDcT0fA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.23.0", + "@mui/x-virtualizer": "0.3.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.23.0.tgz", + "integrity": "sha512-FN7wdqwTxqq1tJBYVz8TA/HMcViuaHS0Jphr4pEjT/8Iuf94Yt3P82WbsTbXyYrgOQDQl07UqE7qWcJetRcHcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@mui/x-virtualizer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.3.0.tgz", + "integrity": "sha512-ScQ3xullKQAtbIT9N7PVhgW9BDTo2Hu8fr/+N08TdRMJcq8bsVJB25mlcHk6zxjlgCuojqaeGfw71S1FOvyCXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1693,6 +1918,190 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@prisma/adapter-pg": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.2.0.tgz", + "integrity": "sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.2.0", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/client": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.2.0.tgz", + "integrity": "sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/client-runtime-utils": "7.2.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.2.0.tgz", + "integrity": "sha512-dn7oB53v0tqkB0wBdMuTNFNPdEbfICEUe82Tn9FoKAhJCUkDH+fmyEp0ClciGh+9Hp2Tuu2K52kth2MTLstvmA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.2.0.tgz", + "integrity": "sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "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": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz", + "integrity": "sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/engines": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.2.0.tgz", + "integrity": "sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0", + "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", + "@prisma/fetch-engine": "7.2.0", + "@prisma/get-platform": "7.2.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3.tgz", + "integrity": "sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.2.0.tgz", + "integrity": "sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0", + "@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3", + "@prisma/get-platform": "7.2.0" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", + "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.8.2" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", + "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", + "devOptional": true, + "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": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1700,6 +2109,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1757,6 +2173,18 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, + "node_modules/@types/pg": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", + "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -2595,6 +3023,16 @@ "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": { "version": "4.11.0", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", @@ -2704,6 +3142,48 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2800,6 +3280,47 @@ "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": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2842,6 +3363,23 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2869,7 +3407,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2971,6 +3509,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -3007,6 +3555,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "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": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3040,6 +3612,64 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", + "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^17.1.0", + "dotenv-expand": "^12.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3055,6 +3685,17 @@ "node": ">= 0.4" } }, + "node_modules/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -3069,6 +3710,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -3701,6 +4352,36 @@ "node": ">=0.10.0" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3848,6 +4529,23 @@ "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": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3888,6 +4586,16 @@ "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": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -3933,6 +4641,13 @@ "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": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -3978,6 +4693,24 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4034,6 +4767,20 @@ "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": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4153,6 +4900,40 @@ "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": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4479,6 +5260,13 @@ "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": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4635,7 +5423,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -4656,6 +5444,16 @@ "node": ">= 0.4" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -4796,6 +5594,16 @@ "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": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4818,6 +5626,13 @@ "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": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4825,6 +5640,13 @@ "dev": true, "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": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4847,6 +5669,22 @@ "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": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4910,6 +5748,40 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "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": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -5031,6 +5903,13 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -5038,6 +5917,26 @@ "dev": true, "license": "MIT" }, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/oauth4webapi": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.3.tgz", @@ -5169,6 +6068,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5281,7 +6187,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5302,6 +6208,118 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5321,6 +6339,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5359,6 +6389,59 @@ "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": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/preact": { "version": "10.24.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", @@ -5388,6 +6471,40 @@ "node": ">= 0.8.0" } }, + "node_modules/prisma": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.2.0.tgz", + "integrity": "sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "7.2.0", + "@prisma/dev": "0.17.0", + "@prisma/engines": "7.2.0", + "@prisma/studio-core": "0.9.0", + "mysql2": "3.15.3", + "postgres": "3.4.7" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5399,6 +6516,25 @@ "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": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5409,6 +6545,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5430,6 +6583,17 @@ ], "license": "MIT" }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", @@ -5473,6 +6637,20 @@ "react-dom": ">=16.6.0" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5496,6 +6674,13 @@ "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": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -5517,6 +6702,22 @@ "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": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -5556,6 +6757,16 @@ "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": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5646,6 +6857,13 @@ "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": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5662,6 +6880,12 @@ "semver": "bin/semver.js" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "devOptional": true + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5773,7 +6997,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -5786,7 +7010,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5868,6 +7092,19 @@ "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": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5886,6 +7123,25 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "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": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -5893,6 +7149,13 @@ "dev": true, "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": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -6097,6 +7360,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -6216,6 +7489,19 @@ "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": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -6298,7 +7584,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6434,11 +7720,35 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "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": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -6549,6 +7859,15 @@ "node": ">=0.10.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -6578,6 +7897,16 @@ "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": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz", diff --git a/package.json b/package.json index 0620a56..b90a023 100644 --- a/package.json +++ b/package.json @@ -6,26 +6,37 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "db:migrate": "prisma migrate dev", + "db:generate": "prisma generate", + "db:studio": "prisma studio" }, "dependencies": { + "@auth/prisma-adapter": "^2.11.1", "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.6", "@mui/material": "^7.3.6", "@mui/material-nextjs": "^7.3.6", + "@mui/x-data-grid": "^8.23.0", + "@prisma/adapter-pg": "^7.2.0", + "@prisma/client": "^7.2.0", "next": "16.1.1", "next-auth": "^5.0.0-beta.30", + "pg": "^8.16.3", "react": "19.2.3", "react-dom": "19.2.3" }, "devDependencies": { "@types/node": "^20", + "@types/pg": "^8.16.0", "@types/react": "^19", "@types/react-dom": "^19", + "dotenv-cli": "^11.0.0", "eslint": "^9", "eslint-config-next": "16.1.1", + "prisma": "^7.2.0", "typescript": "^5" } } diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..1173c5d --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,12 @@ +import { config } from "dotenv"; +config({ path: ".env.local" }); + +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + datasource: { + // This is now the ONLY place where the DB connection string is defined + url: process.env.DATABASE_URL, + }, +}); \ No newline at end of file diff --git a/prisma/migrations/20260107011428_setup_auth_and_files/migration.sql b/prisma/migrations/20260107011428_setup_auth_and_files/migration.sql new file mode 100644 index 0000000..1e4994d --- /dev/null +++ b/prisma/migrations/20260107011428_setup_auth_and_files/migration.sql @@ -0,0 +1,54 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "azureAdUserId" TEXT NOT NULL, + "email" TEXT NOT NULL, + "displayName" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FileNode" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "size" BIGINT, + "isFolder" BOOLEAN NOT NULL DEFAULT false, + "oneDriveId" TEXT, + "path" TEXT NOT NULL, + "orderIndex" INTEGER NOT NULL DEFAULT 0, + "metadata" JSONB NOT NULL DEFAULT '{}', + "description" TEXT, + "ownerId" TEXT NOT NULL, + "parentId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FileNode_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_azureAdUserId_key" ON "User"("azureAdUserId"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "FileNode_oneDriveId_key" ON "FileNode"("oneDriveId"); + +-- CreateIndex +CREATE INDEX "FileNode_parentId_idx" ON "FileNode"("parentId"); + +-- CreateIndex +CREATE INDEX "FileNode_orderIndex_idx" ON "FileNode"("orderIndex"); + +-- CreateIndex +CREATE UNIQUE INDEX "FileNode_ownerId_path_key" ON "FileNode"("ownerId", "path"); + +-- AddForeignKey +ALTER TABLE "FileNode" ADD CONSTRAINT "FileNode_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FileNode" ADD CONSTRAINT "FileNode_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "FileNode"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20260107012848_add_auth_tables/migration.sql b/prisma/migrations/20260107012848_add_auth_tables/migration.sql new file mode 100644 index 0000000..f87133f --- /dev/null +++ b/prisma/migrations/20260107012848_add_auth_tables/migration.sql @@ -0,0 +1,52 @@ +/* + Warnings: + + - You are about to drop the column `displayName` on the `User` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "User" DROP COLUMN "displayName", +ADD COLUMN "emailVerified" TIMESTAMP(3), +ADD COLUMN "image" TEXT, +ADD COLUMN "name" TEXT, +ALTER COLUMN "azureAdUserId" DROP NOT NULL; + +-- CreateTable +CREATE TABLE "Account" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "sessionToken" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..15495e7 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,76 @@ +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id String @id @default(uuid()) + name String? // Renamed from displayName for NextAuth compatibility + email String @unique + emailVerified DateTime? + image String? + azureAdUserId String? @unique + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + nodes FileNode[] + accounts Account[] + sessions Session[] +} + +model Account { + id String @id @default(uuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) +} + +model Session { + id String @id @default(uuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model FileNode { + id String @id @default(uuid()) + name String + size BigInt? + isFolder Boolean @default(false) + oneDriveId String? @unique + path String + orderIndex Int @default(0) + metadata Json @default("{}") + description String? + + ownerId String + owner User @relation(fields: [ownerId], references: [id]) + + parentId String? + parent FileNode? @relation("TreeHierarchy", fields: [parentId], references: [id]) + children FileNode[] @relation("TreeHierarchy") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([ownerId, path]) + @@index([parentId]) + @@index([orderIndex]) +} \ No newline at end of file diff --git a/src/app/dashboard/actions.ts b/src/app/dashboard/actions.ts new file mode 100644 index 0000000..def547a --- /dev/null +++ b/src/app/dashboard/actions.ts @@ -0,0 +1,25 @@ +'use server'; +import { prisma } from "@/lib/prisma"; +import { auth } from "@/auth"; + +export async function getFileNodes() { + const session = await auth(); + + if (!session?.user?.id) { + return []; // Return empty if not logged in + } + + const nodes = await prisma.fileNode.findMany({ + where: { + ownerId: session.user.id, // Only get THIS user's files + }, + orderBy: { + orderIndex: 'asc', + }, + }); + + return nodes.map(node => ({ + ...node, + size: node.size ? Number(node.size) : null, + })); +} \ No newline at end of file diff --git a/src/app/dashboard/dashboard-view.tsx b/src/app/dashboard/dashboard-view.tsx new file mode 100644 index 0000000..915bee1 --- /dev/null +++ b/src/app/dashboard/dashboard-view.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { useState } from "react"; +import { Button, CircularProgress } from "@mui/material"; +import SyncIcon from "@mui/icons-material/Sync"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import { syncOneDrive } from "./sync-actions"; +import { useRouter } from "next/navigation"; + +interface DashboardViewProps { + initialFiles: any[]; +} + +export default function DashboardView({ initialFiles }: DashboardViewProps) { + const [loading, setLoading] = useState(false); + const router = useRouter(); + + const handleSync = async () => { + setLoading(true); + try { + await syncOneDrive(); + // This tells Next.js to re-run the Server Component (page.tsx) + // and fetch the fresh data from Postgres + router.refresh(); + } catch (error) { + console.error("Sync failed:", error); + alert("Failed to sync OneDrive"); + } finally { + setLoading(false); + } + }; + + const columns: GridColDef[] = [ + { field: "name", headerName: "File Name", width: 300 }, + { + field: "metadata", + headerName: "Type", + width: 120, + valueGetter: (params) => params?.type || "Unknown" + }, + { + field: "size", + headerName: "Size (MB)", + width: 120, + valueGetter: (value) => value ? (Number(value) / 1024 / 1024).toFixed(2) : "0" + }, + { field: "updatedAt", headerName: "Last Synced", width: 200 }, + ]; + + return ( +
+
+ +
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index adde291..d2f58cd 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,9 +1,25 @@ -import React from 'react' +import { auth } from "@/auth"; +import { redirect } from "next/navigation"; +import { getFileNodes } from "./actions"; +import DashboardView from "./dashboard-view" + +export default async function DashboardPage() { + const session = await auth(); + + // Guard: If not logged in, go back to home + if (!session?.user) { + redirect("/"); + } + + // Fetch initial files from PostgreSQL + const initialFiles = await getFileNodes(); -function Dashboard() { return ( -
Dashboard
- ) -} - -export default Dashboard \ No newline at end of file +
+

My OneDrive Library

+ + {/* Pass the data to the interactive Client Component */} + +
+ ); +} \ No newline at end of file diff --git a/src/app/dashboard/sync-actions.ts b/src/app/dashboard/sync-actions.ts new file mode 100644 index 0000000..18ab0ab --- /dev/null +++ b/src/app/dashboard/sync-actions.ts @@ -0,0 +1,52 @@ +'use server'; + +import { auth } from "@/auth"; +import { prisma } from "@/lib/prisma"; +import { revalidatePath } from "next/cache"; + +export async function syncOneDrive() { + const session = await auth(); + + if (!session?.user?.id) throw new Error("Unauthorized"); + + // 1. Get the OAuth token from your PostgreSQL 'Account' table + const account = await prisma.account.findFirst({ + where: { userId: session.user.id }, + }); + + if (!account?.access_token) throw new Error("Microsoft account not linked properly"); + + // 2. Fetch the files from Microsoft Graph + const response = await fetch("https://graph.microsoft.com/v1.0/me/drive/root/children", { + headers: { Authorization: `Bearer ${account.access_token}` }, + }); + + const data = await response.json(); + + // 3. The "Sync Loop": Map OneDrive items to your Postgres FileNode table + for (const item of data.value) { + const extension = item.name.split('.').pop()?.toUpperCase() || (item.folder ? 'FOLDER' : 'UNKNOWN'); + + await prisma.fileNode.upsert({ + where: { oneDriveId: item.id }, + update: { + name: item.name, + size: item.size, + metadata: { type: extension }, // Store the "Type" in your JSONB field + }, + create: { + oneDriveId: item.id, + name: item.name, + size: item.size, + isFolder: !!item.folder, + path: item.parentReference.path + '/' + item.name, + ownerId: session.user.id, + metadata: { type: extension }, + } + }); + } + + // Refresh the dashboard UI to show new data + revalidatePath('/dashboard'); + return { success: true, count: data.value.length }; +} \ No newline at end of file diff --git a/src/app/dashboard/upload-actions.ts b/src/app/dashboard/upload-actions.ts new file mode 100644 index 0000000..743ff88 --- /dev/null +++ b/src/app/dashboard/upload-actions.ts @@ -0,0 +1,68 @@ +'use server'; + +import { auth } from "@/auth"; +import { getFreshAccessToken } from "@/lib/auth-utils"; +import { prisma } from "@/lib/prisma"; +import { revalidatePath } from "next/cache"; + +export async function uploadFileAction(formData: FormData) { + const session = await auth(); + if (!session?.user?.id) throw new Error("Unauthorized"); + + const file = formData.get("file") as File; + const folderName = "WebCalibre"; + const accessToken = await getFreshAccessToken(session.user.id); + + // 1. Create/Check WebCalibre Folder + const folderPath = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}`; + const folderCheck = await fetch(folderPath, { + headers: { Authorization: `Bearer ${accessToken}` } + }); + + if (folderCheck.status === 404) { + await fetch(`https://graph.microsoft.com/v1.0/me/drive/root/children`, { + method: "POST", + headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" }, + body: JSON.stringify({ name: folderName, folder: {} }) + }); + } + + // 2. Create Upload Session (Supports files > 4MB) + const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}/${file.name}:/createUploadSession`; + 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, + name: file.name, + size: BigInt(file.size), + isFolder: false, + path: `/${folderName}/${file.name}`, + ownerId: session.user.id, + metadata: { type: file.name.split('.').pop()?.toUpperCase() } + } + }); + + revalidatePath("/dashboard"); + return { success: true }; +} \ No newline at end of file diff --git a/src/app/dashboard/upload.tsx b/src/app/dashboard/upload.tsx new file mode 100644 index 0000000..445ba73 --- /dev/null +++ b/src/app/dashboard/upload.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useState } from "react"; +import { Button, Typography, Box, LinearProgress } from "@mui/material"; +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; +import { uploadFileToOneDrive } from "./upload-actions"; + +export default function UploadForm() { + const [uploading, setUploading] = useState(false); + + async function handleAction(formData: FormData) { + const file = formData.get("file") as File; + if (!file || file.size === 0) return; + + setUploading(true); + try { + await uploadFileToOneDrive(formData); + alert("Uploaded successfully!"); + } catch (error) { + console.error(error); + alert("Upload failed."); + } finally { + setUploading(false); + } + } + + return ( + +
+ { + // Optional: Trigger auto-submit or show filename + }} + /> + + + {uploading && ( + + Uploading to OneDrive & Database... + + + )} + + + + +
+
+ ); +} \ No newline at end of file diff --git a/src/app/dashboard/upload/page.tsx b/src/app/dashboard/upload/page.tsx new file mode 100644 index 0000000..16540d5 --- /dev/null +++ b/src/app/dashboard/upload/page.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { useState } from "react"; +import { Box, Button, Typography, Paper, LinearProgress, Container } from "@mui/material"; +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; +import { uploadFileAction } from "../upload-actions"; + +export default function UploadPage() { + const [file, setFile] = useState(null); + const [loading, setLoading] = useState(false); + + const handleUpload = async () => { + if (!file) return; + setLoading(true); + + const formData = new FormData(); + formData.append("file", file); + + try { + await uploadFileAction(formData); + alert("Book added to WebCalibre!"); + setFile(null); + } catch (err) { + alert("Upload failed. Check console."); + } finally { + setLoading(false); + } + }; + + return ( + + + + Upload to Library + + + Files will be saved in your OneDrive "WebCalibre" folder. + + + + setFile(e.target.files?.[0] || null)} + /> + + + + {loading && } + + + + + ); +} \ No newline at end of file diff --git a/src/app/upload/_actions.ts b/src/app/upload/_actions.ts new file mode 100644 index 0000000..1aeb0b3 --- /dev/null +++ b/src/app/upload/_actions.ts @@ -0,0 +1,112 @@ +'use server'; + +import { auth } from "@/auth"; +import { getFreshAccessToken } from "@/lib/auth-utils"; +import { prisma } from "@/lib/prisma"; +import { revalidatePath } from "next/cache"; + +export async function uploadFileAction(formData: FormData) { + const session = await auth(); + if (!session?.user?.id) throw new Error("Unauthorized"); + + const file = formData.get("file") as File; + if (!file) throw new Error("No file selected"); + + const accessToken = await getFreshAccessToken(session.user.id); + const folderName = "WebCalibre"; + + // --- 1. CHECK/CREATE THE WEBCALIBRE FOLDER --- + // We check if the folder exists at the root of the user's OneDrive + const folderCheckUrl = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}`; + const folderCheck = await fetch(folderCheckUrl, { + headers: { Authorization: `Bearer ${accessToken}` } + }); + + if (folderCheck.status === 404) { + console.log(`📂 Folder '${folderName}' not found. Creating it...`); + const createFolderRes = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root/children`, { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + name: folderName, + folder: {}, // Empty object tells Graph to create a folder + "@microsoft.graph.conflictBehavior": "fail" + }) + }); + + if (!createFolderRes.ok) { + const errorData = await createFolderRes.json(); + console.error("❌ Folder Creation Error:", errorData); + throw new Error("Could not create WebCalibre folder on OneDrive."); + } + } + + // --- 2. CREATE UPLOAD SESSION --- + // encodeURIComponent is vital for filenames with spaces or special characters + const sessionUrl = `https://graph.microsoft.com/v1.0/me/drive/root:/${folderName}/${encodeURIComponent(file.name)}:/createUploadSession`; + + const sessionRes = await fetch(sessionUrl, { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + item: { + "@microsoft.graph.conflictBehavior": "rename", // If file exists, name it "Book 1.pdf" + name: file.name + } + }) + }); + + const sessionData = await sessionRes.json(); + if (!sessionRes.ok) { + console.error("❌ Session Error:", sessionData); + throw new Error(sessionData.error?.message || "OneDrive session failed"); + } + + const { uploadUrl } = sessionData; + + // --- 3. UPLOAD THE DATA BYTES --- + 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) { + const uploadError = await uploadRes.json(); + console.error("❌ Upload Error:", uploadError); + throw new Error("Chunk upload failed"); + } + + const driveItem = await uploadRes.json(); + + // --- 4. RECORD IN POSTGRESQL (PRISMA) --- + await prisma.fileNode.create({ + data: { + oneDriveId: driveItem.id, + name: file.name, + size: BigInt(file.size), + isFolder: false, + path: `/${folderName}/${file.name}`, + ownerId: session.user.id, + metadata: { + type: file.name.split('.').pop()?.toUpperCase() || "UNKNOWN", + mimeType: file.type + } + } + }); + + // Revalidate ensures the dashboard list updates immediately + revalidatePath("/dashboard"); + + return { success: true }; +} \ No newline at end of file diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx new file mode 100644 index 0000000..120865d --- /dev/null +++ b/src/app/upload/page.tsx @@ -0,0 +1,17 @@ +import { auth } from "@/auth"; +import { redirect } from "next/navigation"; +import UploadView from "./upload-view"; +import { Container } from "@mui/material"; + +export default async function UploadPage() { + const session = await auth(); + + // Guard: Must be logged in to upload + if (!session) redirect("/"); + + return ( + + + + ); +} \ No newline at end of file diff --git a/src/app/upload/upload-view.tsx b/src/app/upload/upload-view.tsx new file mode 100644 index 0000000..013328c --- /dev/null +++ b/src/app/upload/upload-view.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { useState } from "react"; +import { Box, Button, Typography, Paper, LinearProgress, Stack } from "@mui/material"; +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; +import { uploadFileAction } from "./_actions"; + +/** + * Helper function to convert raw bytes into a human-readable string. + * This helps the user understand exactly how large their e-book is. + */ +const formatFileSize = (bytes: number) => { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + // Returns something like "1.45 MB" or "850 KB" + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; + +export default function UploadView({ user }: { user: any }) { + const [file, setFile] = useState(null); + const [status, setStatus] = useState<'idle' | 'uploading' | 'success'>('idle'); + + const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB Limit + + const handleUpload = async () => { + if (!file) return; + if (file.size > MAX_FILE_SIZE) { + alert(`File is too large! Maximum size allowed is ${formatFileSize(MAX_FILE_SIZE)}.`); + return; + } + setStatus('uploading'); + + const formData = new FormData(); + formData.append("file", file); + + try { + await uploadFileAction(formData); + setStatus('success'); + setFile(null); + } catch (err) { + // If the server action fails, it usually prints details in the terminal + alert("Upload failed. Ensure the 'WebCalibre' folder can be created and OneDrive has space."); + setStatus('idle'); + } + }; + + return ( + + + Upload to WebCalibre + + + Adding books as {user.name} + + + + {/* Dropzone/Selection Area */} + + { + setFile(e.target.files?.[0] || null); + setStatus('idle'); + }} + /> + + + {/* New: Enhanced File Info Display */} + {file && ( + + + Selected: {file.name} + + + File Size: {formatFileSize(file.size)} + + + )} + + + {/* Progress Indicator */} + {status === 'uploading' && ( + + + Connecting to OneDrive & Uploading... + + + + )} + + {/* Action Button */} + + + {/* Success Feedback */} + {status === 'success' && ( + + ✅ Successfully uploaded to your library! + + )} + + + ); +} \ No newline at end of file diff --git a/src/auth.config.ts b/src/auth.config.ts new file mode 100644 index 0000000..9fe15ef --- /dev/null +++ b/src/auth.config.ts @@ -0,0 +1,28 @@ +import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id"; +import type { NextAuthConfig } from "next-auth"; + +export default { + providers: [ + MicrosoftEntraID({ + clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID, + clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET, + issuer: process.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER, + authorization: { + params: { + scope: "openid profile offline_access email Files.ReadWrite Files.Read", + prompt: "consent", // Forces Microsoft to show the permission screen + access_type: "offline", + }, + }, + profile(profile) { + return { + id: profile.sub, + name: profile.name, + email: profile.email, + image: null, + azureAdUserId: profile.oid ?? profile.sub, + }; + }, + }), + ], +} satisfies NextAuthConfig; \ No newline at end of file diff --git a/src/auth.ts b/src/auth.ts index 1bf98f9..8e547bf 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,21 +1,39 @@ -import NextAuth from "next-auth" -import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id" - +import NextAuth from "next-auth"; +import { PrismaAdapter } from "@auth/prisma-adapter"; +import { prisma } from "@/lib/prisma"; +import authConfig from "./auth.config"; + export const { handlers, signIn, signOut, auth } = NextAuth({ - providers: [ - MicrosoftEntraID({ - clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID, - clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET, - // Pass tenantId as 'common' to support multi-tenant + personal accounts - tenantId: process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID, - authorization: { - params: { - // Explicitly ask for these scopes for OneDrive access later - scope: "openid profile email offline_access User.Read Files.ReadWrite", - }, - }, - }) - ], - // Required for Next.js 15/16 and Nginx production environments - trustHost: true, -}) \ No newline at end of file + adapter: PrismaAdapter(prisma), + session: { strategy: "jwt" }, + ...authConfig, + callbacks: { + async jwt({ token, account, user }) { + // On the first sign in, 'account' contains the refresh_token + if (account) { + token.accessToken = account.access_token; + token.refreshToken = account.refresh_token; + token.expiresAt = account.expires_at; + } + if (user) { + token.sub = user.id; + } + return token; + }, + async session({ session, token }) { + if (session?.user && token.sub) { + session.user.id = token.sub; + } + return session; + }, + }, + // Adding events can help debug if the account is actually linking + events: { + async linkAccount({ account, user }) { + console.log("🔗 Account linked successfully for user:", user.id); + if (!account.refresh_token) { + console.warn("⚠️ WARNING: No refresh_token received in linkAccount event!"); + } + } + } +}); \ No newline at end of file diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index 2e2e640..d8038e3 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -1,9 +1,15 @@ 'use client'; -import React from 'react'; -import { AppBar, Toolbar, Typography, Button, Box, Container, Avatar, IconButton } from '@mui/material'; -import LibraryBooksIcon from '@mui/icons-material/LibraryBooks'; +import React, { useState } from 'react'; +import { + AppBar, Toolbar, Typography, Button, Box, Container, + Avatar, IconButton, Drawer, List, ListItem, + ListItemButton, ListItemIcon, ListItemText, Divider +} from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; +import LibraryBooksIcon from '@mui/icons-material/LibraryBooks'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import DashboardIcon from '@mui/icons-material/Dashboard'; import Link from 'next/link'; import { signIn, signOut } from "next-auth/react"; @@ -16,72 +22,90 @@ interface NavbarProps { } export default function Navbar({ user }: NavbarProps) { + const [isDrawerOpen, setIsDrawerOpen] = useState(false); const isLoggedIn = !!user; + const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { + if (event.type === 'keydown' && ((event as React.KeyboardEvent).key === 'Tab' || (event as React.KeyboardEvent).key === 'Shift')) { + return; + } + setIsDrawerOpen(open); + }; + + const navItems = [ + { text: 'Dashboard', icon: , href: '/dashboard' }, + { text: 'Library', icon: , href: '/library' }, + { text: 'Upload File', icon: , href: '/upload' }, + ]; + return ( - - - - {/* Menu Icon for the future AppDrawer */} - - - + <> + + + + + + - - WebCalibre 2 - + + WebCalibre 2 + - - {isLoggedIn ? ( - <> - {/* User Info: Name and Small Email */} - - - {user.name} - - - {user.email} - - - - {/* Profile Picture */} - - {user.name?.charAt(0)} - - - + + ) : ( + - - ) : ( - // Login button triggers the Microsoft flow directly - - )} + )} + + + + + + {/* The Drawer (Sidebar) */} + + + + + Navigation - - - + + + {navItems.map((item) => ( + + + {item.icon} + + + + ))} + + + + ); } \ No newline at end of file diff --git a/src/lib/auth-utils.ts b/src/lib/auth-utils.ts new file mode 100644 index 0000000..451aa0f --- /dev/null +++ b/src/lib/auth-utils.ts @@ -0,0 +1,55 @@ +import { prisma } from "@/lib/prisma"; + +export async function getFreshAccessToken(userId: string) { + // 1. Find the account in PostgreSQL + const account = await prisma.account.findFirst({ + where: { userId }, + }); + + if (!account || !account.refresh_token) { + throw new Error("No refresh token available. User might need to re-login."); + } + + // 2. Check if the token is expired (with a 1-minute buffer) + // account.expires_at is usually in seconds, so we multiply by 1000 + const isExpired = (account.expires_at ?? 0) * 1000 < Date.now() + 60000; + + if (!isExpired && account.access_token) { + return account.access_token; + } + + // 3. If expired, request a new one from Microsoft + console.log("🔄 Access token expired. Refreshing for user:", userId); + + try { + const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + client_id: process.env.AUTH_MICROSOFT_ENTRA_ID_ID!, + client_secret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET!, + grant_type: "refresh_token", + refresh_token: account.refresh_token, + }), + }); + + const tokens = await response.json(); + + if (!response.ok) throw tokens; + + // 4. Update the Account table with the new tokens + await prisma.account.update({ + where: { id: account.id }, + data: { + access_token: tokens.access_token, + expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in), + refresh_token: tokens.refresh_token ?? account.refresh_token, + }, + }); + + return tokens.access_token; + } catch (error) { + console.error("❌ Failed to refresh Microsoft token:", error); + throw new Error("RefreshAccessTokenError"); + } +} \ No newline at end of file diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..db586b8 --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,37 @@ +import { PrismaClient } from '@prisma/client'; +import { PrismaPg } from '@prisma/adapter-pg'; +import { Pool } from 'pg'; + +// 1. Setup the connection pool using your .env.local variable +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); +const adapter = new PrismaPg(pool); + +// 2. Define the singleton logic +const prismaClientSingleton = () => { + const client = new PrismaClient({ adapter }); + + // ✅ HEALTH CHECK: Only runs once per client initialization + client.$connect() + .then(() => console.log("✅ Prisma 7 connected to PostgreSQL successfully")) + .catch((err) => console.error("❌ Prisma connection error:", err)); + + return client; +}; + +type PrismaClientSingleton = ReturnType; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClientSingleton | undefined; +}; + +// 3. Export the client (reusing existing one if it exists) +export const prisma = globalForPrisma.prisma ?? prismaClientSingleton(); + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; + +// BigInt fix for JSON serialization (needed for file sizes) +if (typeof BigInt !== 'undefined') { + (BigInt.prototype as any).toJSON = function () { + return Number(this); + }; +} \ No newline at end of file diff --git a/src/proxy.ts b/src/proxy.ts index 72accdc..863b06c 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { auth } from "@/auth"; +import {auth} from "@/auth"; const protectedRoutes = ["/dashboard", "/profile"]; const apiAuthPrefix = "/api/auth";