diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..b512c09d47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000000..1dcef2d9f2 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,2 @@ +node_modules +.env \ No newline at end of file diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000000..2c11e22790 --- /dev/null +++ b/backend/.env @@ -0,0 +1,3 @@ +DATABASE_URL=postgresql://postgres:postgres@localhost:5432/bookmyvenue +JWT_SECRET= your_jwt_secret_key +FRONTEND_URL=http://localhost:5173 \ No newline at end of file diff --git a/backend/.prettierrc b/backend/.prettierrc new file mode 100644 index 0000000000..ad5b5e1248 --- /dev/null +++ b/backend/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "printWidth": 100 +} diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000000..7e57926c6c --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,13 @@ +FROM node:20 + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +EXPOSE 5005 + +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/backend/drizzle.config.js b/backend/drizzle.config.js new file mode 100644 index 0000000000..11e276ca8a --- /dev/null +++ b/backend/drizzle.config.js @@ -0,0 +1,11 @@ +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + out: './drizzle', + schema: './src/models/index.js', + dialect: 'postgresql', + dbCredentials: { + url: 'postgresql://postgres:postgres@localhost:5432/bookmyvenue', +}, +}); \ No newline at end of file diff --git a/backend/drizzle/0000_nervous_midnight.sql b/backend/drizzle/0000_nervous_midnight.sql new file mode 100644 index 0000000000..f93bd01227 --- /dev/null +++ b/backend/drizzle/0000_nervous_midnight.sql @@ -0,0 +1,8 @@ +CREATE TABLE "users" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "username" varchar(255) NOT NULL, + "email" varchar(255) NOT NULL, + "password" varchar(255) NOT NULL, + "salt" text NOT NULL, + CONSTRAINT "users_email_unique" UNIQUE("email") +); diff --git a/backend/drizzle/meta/0000_snapshot.json b/backend/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000000..92c2401ce9 --- /dev/null +++ b/backend/drizzle/meta/0000_snapshot.json @@ -0,0 +1,71 @@ +{ + "id": "c7508fa4-824c-4a94-8492-0985c35edf7c", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/backend/drizzle/meta/_journal.json b/backend/drizzle/meta/_journal.json new file mode 100644 index 0000000000..6bf242ec96 --- /dev/null +++ b/backend/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1780129487718, + "tag": "0000_nervous_midnight", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/backend/index.js b/backend/index.js new file mode 100644 index 0000000000..cbfa39b517 --- /dev/null +++ b/backend/index.js @@ -0,0 +1,99 @@ +import express from 'express'; +import dotenv from 'dotenv'; +import routes from './src/routes/index.js'; +import { globalErrorHandler } from './src/handlers/error_handlers.js'; +import cors from 'cors'; +import cookieParser from 'cookie-parser'; +import { WebSocketServer } from 'ws'; +import { addClient, removeClient } from './src/utils/wsClient.js'; +import { verifyToken } from './src/utils/utils.js'; +import {parseCookies} from './src/utils/utils.js'; + +import conversationService from './src/services/conversationService.js'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 5005; + +app.use(express.json()); +app.use(cookieParser()); +app.use(cors({ + origin: 'http://localhost:5173', + credentials: true, +})); + +app.use((req, res, next) => { + console.log(`Incoming Request: ${req.method} ${req.url}`); + next(); +}); + +app.get('/', (req, res) => { + res.send('Server is up and running'); +}); + +app.use(routes); +app.use(globalErrorHandler); + +// capture the return value of app.listen as server +const server = app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); + +const wss = new WebSocketServer({ noServer: true }); + +server.on('upgrade', (req, socket, head) => { + try { + const cookies = parseCookies(req); + const token = cookies['accessToken']; + + if (!token) { + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + socket.destroy(); + return; + } + + const decoded = verifyToken(token); + req.user = { id: decoded.userId, email: decoded.email, role: decoded.role }; + + wss.handleUpgrade(req, socket, head, (ws) => { + wss.emit('connection', ws, req); + }); + } catch (err) { + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + socket.destroy(); + } +}); + +wss.on('connection', (ws, req) => { + const userId = req.user.id; + addClient(userId, ws); + console.log(`WS connected: ${userId}`); + + ws.on('message', async (data) => { + try { + const { type, payload } = JSON.parse(data); + if (type === 'SEND_MESSAGE') { + const { conversationId, content, venueId } = payload; + await conversationService.sendMessage( + conversationId, + userId, + content, + venueId + ); + } + } catch (err) { + console.error(`Error handling message for ${userId}:`, err); + } + }); + + ws.on('close', () => { + removeClient(userId, ws); + console.log(`WS disconnected: ${userId}`); + }); + + ws.on('error', (err) => { + console.error(`WS error for ${userId}:`, err); + removeClient(userId, ws); + }); +}); \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000000..b2082fc346 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,3485 @@ +{ + "name": "backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@phonepe-pg/pg-sdk-node": "^2.0.6", + "cookie-parser": "^1.4.7", + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "drizzle-orm": "^0.45.2", + "express": "^5.2.1", + "jsonwebtoken": "^9.0.3", + "nodemon": "^3.1.14", + "pg": "^8.21.0", + "pg-sdk-node": "^2.0.2", + "ws": "^8.21.0", + "zod": "^4.4.3" + }, + "devDependencies": { + "@types/pg": "^8.20.0", + "drizzle-kit": "^0.31.10", + "prettier": "^3.0.0", + "tsx": "^4.22.3" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@phonepe-pg/pg-sdk-node": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@phonepe-pg/pg-sdk-node/-/pg-sdk-node-2.0.6.tgz", + "integrity": "sha512-W+p78AE6Ci/UXYRmqig9orK70x62Tzag7YLYM2dO5UUp3t4+Sv4NQvhJrYbYwiQiqcQ/2ShFEELxcgKm1Ra5sw==", + "license": "Apache-2.0", + "dependencies": { + "async-mutex": "^0.5.0", + "axios": "^1.2.1", + "axios-retry": "^3.2.0", + "class-transformer": "^0.4.0", + "class-transformer-validator": "^0.9.1", + "class-validator": "^0.14.1" + } + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios-retry": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", + "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.15.4", + "is-retry-allowed": "^2.2.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/class-transformer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", + "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==", + "license": "MIT" + }, + "node_modules/class-transformer-validator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/class-transformer-validator/-/class-transformer-validator-0.9.1.tgz", + "integrity": "sha512-83/KFCyd6UiiwH6PlQS5y17O5TTx58CawvNI+XdrMs0Ig9QI5kiuzRqGcC/WrEpd1F7i4KIxCwdn6m4B6fl0jw==", + "license": "MIT", + "peerDependencies": { + "class-transformer": ">=0.2.3", + "class-validator": ">=0.12.0" + } + }, + "node_modules/class-validator": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", + "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drizzle-kit": { + "version": "0.31.10", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "tsx": "^4.21.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", + "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.6.tgz", + "integrity": "sha512-NdB6O6QvlGMCoG003m0YIKG2+Xw7DjmCZhmc1RH+K6HncADUbRf8TZeLegxBBN1VFyPHcNpPTKpIhYLXzJVy1Q==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pg": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", + "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.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", + "license": "MIT" + }, + "node_modules/pg-sdk-node": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pg-sdk-node/-/pg-sdk-node-2.0.2.tgz", + "integrity": "sha512-kAMI1zzfO0ojUWytPInTOc+MWHeuHI56tfMLzPDE3R0puYbvzPgwrCFG72AAU76L7jYAttU1XNukuduEo/J6SA==", + "license": "ISC", + "dependencies": { + "async-mutex": "^0.5.0", + "axios": "^1.2.1", + "axios-retry": "^3.2.0", + "class-transformer": "^0.4.0", + "class-transformer-validator": "^0.9.1", + "class-validator": "^0.14.1" + } + }, + "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/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/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/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/prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.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/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "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/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000000..75f478ed13 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,38 @@ +{ + "name": "backend", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "nodemon index.js", + "db:push": "drizzle-kit push", + "db:studio": "drizzle-kit studio", + "seed": "node src/seeds/index.js", + "format": "prettier --write \"src/**/*.js\"" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@phonepe-pg/pg-sdk-node": "^2.0.6", + "cookie-parser": "^1.4.7", + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "drizzle-orm": "^0.45.2", + "express": "^5.2.1", + "jsonwebtoken": "^9.0.3", + "nodemon": "^3.1.14", + "pg": "^8.21.0", + "pg-sdk-node": "^2.0.2", + "ws": "^8.21.0", + "zod": "^4.4.3" + }, + "devDependencies": { + "@types/pg": "^8.20.0", + "drizzle-kit": "^0.31.10", + "prettier": "^3.0.0", + "tsx": "^4.22.3" + } +} diff --git a/backend/src/controllers/analyticController.js b/backend/src/controllers/analyticController.js new file mode 100644 index 0000000000..29a05903b1 --- /dev/null +++ b/backend/src/controllers/analyticController.js @@ -0,0 +1,9 @@ +import { sendResponse } from '../handlers/response_handlers.js'; +import analyticService from '../services/analyticsServices.js'; + +export default { + adminDashboardStats: async (req, res) => { + const response = await analyticService.adminDashboardStats(); + sendResponse(res, { data: response }); + }, +}; diff --git a/backend/src/controllers/authController.js b/backend/src/controllers/authController.js new file mode 100644 index 0000000000..881f8cf951 --- /dev/null +++ b/backend/src/controllers/authController.js @@ -0,0 +1,60 @@ +import authService from '../services/authServices.js'; +import { registerSchema } from '../validations/authValidations.js'; +import { AppError } from '../handlers/error_handlers.js'; +import { sendResponse } from '../handlers/response_handlers.js'; + +export default { + login: async function (req, res) { + const payload = req.body; + const response = await authService.login(payload); + console.log('Login response from service:', response); + res.cookie('accessToken', response.token, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 60 * 60 * 1000, + }); + sendResponse(res, response); + }, + + logout: async function (req,res){ + res.clearCookie('accessToken', { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 60 * 60 * 1000, + }); + sendResponse(res, { data: null, message: 'Logged out successfully' }) + }, + + register: async function (req, res) { + const payload = registerSchema.parse(req.body); + const response = await authService.register(payload); + sendResponse(res, response); + }, + + getCurrentUser: async function (req, res) { + if (!req.user) { + throw new AppError({ + message: 'User not found', + statusCode: 404, + errorCode: 'USER_NOT_FOUND', + }); + } + const response = { data: req.user }; + sendResponse(res, response); + }, + + adminLogin: async function (req,res){ + const response = await authService.adminLogin(req.body) + + res.cookie('accessToken', response.token, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 60 * 60 * 1000, + }) + + sendResponse(res, response) + } +}; diff --git a/backend/src/controllers/bookingController.js b/backend/src/controllers/bookingController.js new file mode 100644 index 0000000000..59273ff1e6 --- /dev/null +++ b/backend/src/controllers/bookingController.js @@ -0,0 +1,54 @@ +import { db } from '../db/index.js'; +import { bookingsTable } from '../models/bookingModel.js'; +import bookingServices from '../services/bookingServices.js'; +import { sendResponse } from '../handlers/response_handlers.js'; + +export default { + checkAvailability: async function (req, res) { + const venueId = req.params.id; + const month = req.query.month; + const response = await bookingServices.checkAvailability(venueId, month); + sendResponse(res, { data: response }); + }, + + bookVenue: async function (req, res) { + const { venueId, startDate, endDate, startTime, endTime } = req.body; + const response = await bookingServices.bookVenue( + req.user.id, + venueId, + startDate, + endDate, + startTime, + endTime + ); + sendResponse(res, { data: response, statusCode: 201 }); + }, + + verifyPayment: async (req, res) => { + const { bookingId } = req.params; + + const result = await bookingServices.verifyPayment(bookingId); + + sendResponse(res, { + statusCode: 200, + message: 'Payment status fetched', + data: result, + }); + }, + + getUserBookings: async(req,res) => { + const userId = req.params.userId; + const result = await bookingServices.getUserBookings(userId); + sendResponse(res,{ + data: result + }) + }, + + getOwnerBookings: async(req,res) => { + const ownerId = req.params.ownerId; + const result = await bookingServices.getOwnerBookings(ownerId); + sendResponse(res,{ + data: result + }); + } +}; diff --git a/backend/src/controllers/conversationController.js b/backend/src/controllers/conversationController.js new file mode 100644 index 0000000000..a4235801a8 --- /dev/null +++ b/backend/src/controllers/conversationController.js @@ -0,0 +1,61 @@ +import conversationService from '../services/conversationService.js'; +import { sendResponse } from '../handlers/response_handlers.js'; +import { AppError } from '../handlers/error_handlers.js'; + +export default { + findOrCreate: async function (req, res, next) { + try { + const { ownerId, userId: targetUserId } = req.body; + const { id: requesterId, role } = req.user; + + let userId; + let ownerIdFinal; + + if (role === 'owner') { + if (!targetUserId) { + throw new AppError({ + message: 'userId is required for owners', + statusCode: 400, + }); + } + userId = targetUserId; + ownerIdFinal = requesterId; + } else { + if (!ownerId) { + throw new AppError({ + message: 'ownerId is required', + statusCode: 400, + }); + } + userId = requesterId; + ownerIdFinal = ownerId; + } + + const conversation = await conversationService.findOrCreate(userId, ownerIdFinal); + sendResponse(res, { data: conversation }); + } catch (err) { + next(err); + } + }, + + getMessages: async function (req, res, next) { + try { + const { id: conversationId } = req.params; + const { cursor, limit } = req.query; + const messages = await conversationService.getMessages(conversationId, cursor, limit); + sendResponse(res, { data: messages }); + } catch (err) { + next(err); + } + }, + + getConversations: async function (req, res, next) { + try { + const { id: userId, role } = req.user; + const conversations = await conversationService.getConversations(userId, role); + sendResponse(res, { data: conversations }); + } catch (err) { + next(err); + } + }, +}; diff --git a/backend/src/controllers/favouriteController.js b/backend/src/controllers/favouriteController.js new file mode 100644 index 0000000000..aa12f7ccfd --- /dev/null +++ b/backend/src/controllers/favouriteController.js @@ -0,0 +1,28 @@ +import { sendResponse } from "../handlers/response_handlers.js"; +import favoriteServices from "../services/favouriteServices.js" + + + +export default { + addFavorite: async function(req,res){ + const userId = req.user.id; + const venueId = req.params.venueId; + const response = await favoriteServices.addFavorite(userId,venueId); + sendResponse(res,{data: response, statusCode:201}) + + }, + + deleteFavorite: async function (req,res){ + const venueId = req.params.venueId; + const userId = req.user.id; + await favoriteServices.deleteFavorite(venueId,userId); + sendResponse(res,{statusCode: 204}) + }, + + getFavorites: async function (req,res){ + const userId = req.user.id; + const result = await favoriteServices.getFavorites(userId); + sendResponse(res,{data: result}) + } + +} \ No newline at end of file diff --git a/backend/src/controllers/notificationController.js b/backend/src/controllers/notificationController.js new file mode 100644 index 0000000000..0735fd5ae2 --- /dev/null +++ b/backend/src/controllers/notificationController.js @@ -0,0 +1,26 @@ +import { addClient, removeClient } from '../utils/sseClient.js'; +import notificationService from '../services/notificationService.js'; +import { sendResponse } from '../handlers/response_handlers.js'; + +export default { + setStream: async function (req, res) { + console.log('SSE connection attempt', req.user) + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.flushHeaders() + addClient(req.user.id, res); + // query unread notifications for this user + // write each one to res + const unReadNotifications = await notificationService.getNotifications(req.user.id); + + unReadNotifications.forEach(r=> res.write(`data: ${JSON.stringify(r)}\n\n`)) + + req.on('close', () => removeClient(req.user.id, res)); + }, + + markAllRead: async function (req, res) { + await notificationService.markAllAsRead(req.user.id); + sendResponse(res,{statusCode:204}) + }, +}; diff --git a/backend/src/controllers/venueController.js b/backend/src/controllers/venueController.js new file mode 100644 index 0000000000..c907485e56 --- /dev/null +++ b/backend/src/controllers/venueController.js @@ -0,0 +1,89 @@ +import { AppError } from '../handlers/error_handlers.js'; +import { sendResponse } from '../handlers/response_handlers.js'; +import venueService from '../services/venueServices.js'; +import { venueSchema } from '../validations/venueValidation.js'; + +export default { + addVenue: async function (req, res) { + console.log(req.body,"reqBodyyy") + const payload = venueSchema.parse(req.body); + const result = await venueService.addVenue({ ...payload, ownerId: req.user.id }); + console.log("result",result) + sendResponse(res, 201, { message: 'Venue added successfully', data: result }); + }, + getOwnerVenues: async function (req, res) { + const result = await venueService.getOwnerVenues(req.user.id); + sendResponse(res, { data: result }); + }, + + getVenues: async function (req, res) { + const payload = { ...req.query }; + const isAdmin = req.user.role === 'admin'; + if (!isAdmin) { + delete payload.includeInactive; + } + const result = await venueService.getVenues(payload, { isAdmin }); + sendResponse(res, { + data: result.rows, + meta: { total: result.total, page: result.page, pageSize: result.pageSize }, + }); + }, + + updateVenue: async function(req,res){ + const venueId = req.params.id; + console.log(req.params.id,"req.params.id") + const payload = req.body; + const response = await venueService.updateVenue(payload, venueId); + sendResponse(res,{ + data: response, + message: "venue details updated" + }); + }, + + getVenueDetails: async function (req, res) { + const venueId = req.params.id; + const result = await venueService.getVenueDetails(venueId); + sendResponse(res, { data: result }); + }, + + getPendingVenues: async function (req, res) { + const response = await venueService.getPendingVenues(); + sendResponse(res, { data: response }); + }, + + approveVenue: async function (req, res) { + const venueId = req.params.id; + const response = await venueService.approveVenue(venueId); + sendResponse(res, { data: response, message: 'Venue approved' }); + }, + + rejectVenue: async function (req, res) { + const venueId = req.params.id; + const reason = req.body.reason; + const response = await venueService.rejectVenue(venueId, reason); + sendResponse(res, { data: response, message: 'Venue rejected' }); + }, + + deactivateVenue: async function (req, res) { + const venueId = req.params.id; + const response = await venueService.deactivateVenue(venueId); + sendResponse(res, { data: response, message: 'Venue deactivated' }); + }, + + activateVenue: async function (req, res) { + const venueId = req.params.id; + const response = await venueService.activateVenue(venueId); + sendResponse(res, { data: response, message: 'Venue activated' }); + }, + + checkSubmission: async function (req, res) { + const venueId = req.params.id; + const response = await venueService.checkSubmission(venueId); + sendResponse(res, { data: response, message: 'Venue check completed' }); + }, + + getAmenities: async function (req,res){ + const response = await venueService.getAmenities(); + sendResponse(res,{data: response}); + } +}; diff --git a/backend/src/db/index.js b/backend/src/db/index.js new file mode 100644 index 0000000000..320771ce31 --- /dev/null +++ b/backend/src/db/index.js @@ -0,0 +1,7 @@ +import 'dotenv/config'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import * as schema from '../models/index.js'; +console.log(Object.keys(schema), 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + +export const db = drizzle(process.env.DATABASE_URL, { schema }); +export default db; diff --git a/backend/src/handlers/error_handlers.js b/backend/src/handlers/error_handlers.js new file mode 100644 index 0000000000..64c0c027e3 --- /dev/null +++ b/backend/src/handlers/error_handlers.js @@ -0,0 +1,42 @@ +import { ZodError } from 'zod'; + +export class AppError extends Error { + constructor({ message, statusCode = 500, errorCode, metadata }) { + super(message); + + this.statusCode = statusCode; + this.errorCode = errorCode; + this.metadata = metadata; + this.isOperational = true; + + Error.captureStackTrace(this, this.constructor); + } +} + +export const globalErrorHandler = (err, req, res, next) => { + console.error(err); + if (err instanceof ZodError) { + return res.status(400).json({ + success: false, + message: err.issues.map((issue) => issue.message).join(', '), + errorCode: 'VALIDATION_ERROR', + }); + } + + if (err instanceof AppError) { + return res.status(err.statusCode).json({ + success: false, + message: err.message, + errorCode: err.errorCode, + metadata: err.metadata, + }); + } + + return res.status(500).json({ + success: false, + message: 'Internal Server Error', + }); +}; + +export const catchErrors = (fn) => (req, res, next) => + Promise.resolve(fn(req, res, next)).catch(next); diff --git a/backend/src/handlers/response_handlers.js b/backend/src/handlers/response_handlers.js new file mode 100644 index 0000000000..1a88aa07ba --- /dev/null +++ b/backend/src/handlers/response_handlers.js @@ -0,0 +1,12 @@ +export const sendResponse = ( + res, + { statusCode = 200, success = true, message = 'Success', data = null, meta = null } = {} +) => { + console.log('Sending response with status code:', data); + return res.status(statusCode).json({ + success, + message, + data, + meta, + }); +}; diff --git a/backend/src/middlewares/authentication.js b/backend/src/middlewares/authentication.js new file mode 100644 index 0000000000..853efa7c27 --- /dev/null +++ b/backend/src/middlewares/authentication.js @@ -0,0 +1,51 @@ +import jwt from 'jsonwebtoken'; +import { AppError } from '../handlers/error_handlers.js'; +import userService from '../services/authServices.js'; + +export const isAuthenticated = async (req, res, next) => { + try { + const token = req.cookies.accessToken; + + if (!token) { + throw new AppError({ + message: 'Unauthorized', + statusCode: 401, + errorCode: 'UNAUTHORIZED', + }); + } + + const decoded = jwt.verify(token, process.env.JWT_SECRET); + + const user = await userService.getById(decoded.userId); + + if (!user) { + throw new AppError({ + message: 'User not found', + statusCode: 401, + errorCode: 'USER_NOT_FOUND', + }); + } + + req.user = user; + + next(); + } catch (err) { + next(err); + } +}; + +export const requireRole = (...roles) => { + return (req, res, next) => { + console.log(req.user.role,roles,"ggggggggggggggggggggggggggggggg") + if (!roles.includes(req.user.role)) { + return next( + new AppError({ + message: `Forbidden: ${roles.join(' or ')} only`, + statusCode: 403, + errorCode: 'FORBIDDEN', + }) + ); + } + next(); + }; +}; diff --git a/backend/src/models/amenityModel.js b/backend/src/models/amenityModel.js new file mode 100644 index 0000000000..afe0671b5c --- /dev/null +++ b/backend/src/models/amenityModel.js @@ -0,0 +1,25 @@ +import { pgTable, uuid, varchar, primaryKey } from 'drizzle-orm/pg-core'; +import { venuesTable } from './venueModel.js'; + +export const amenities = pgTable('amenities', { + id: uuid('id').defaultRandom().primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + slug: varchar('slug', { length: 100 }).notNull().unique(), + icon: varchar('icon', { length: 100 }), + category: varchar('category', { length: 50 }), +}); + +export const venueAmenities = pgTable( + 'venue_amenities', + { + venueId: uuid('venue_id') + .references(() => venuesTable.id) + .notNull(), + amenityId: uuid('amenity_id') + .references(() => amenities.id) + .notNull(), + }, + (table) => ({ + pk: primaryKey({ columns: [table.venueId, table.amenityId] }), + }) +); diff --git a/backend/src/models/bookingModel.js b/backend/src/models/bookingModel.js new file mode 100644 index 0000000000..c9b9c3db23 --- /dev/null +++ b/backend/src/models/bookingModel.js @@ -0,0 +1,34 @@ +import { + pgTable, + varchar, + uuid, + text, + primaryKey, + date, + numeric, + jsonb, + timestamp, + time +} from 'drizzle-orm/pg-core'; +import { venuesTable } from './venueModel.js'; +import { usersTable } from './userModel.js'; + +export const bookingsTable = pgTable('bookings', { + id: uuid('id').primaryKey().defaultRandom(), + venueId: uuid('venue_id') + .references(() => venuesTable.id) + .notNull(), + bookerId: uuid('booker_id') + .references(() => usersTable.id) + .notNull(), + startDate: date('start_date').notNull(), // date, not timestamp — no time needed + endDate: date('end_date').notNull(), + status: varchar('status').notNull().default('pending'), + totalAmount: numeric('total_amount', { precision: 10, scale: 2 }).notNull(), + pricingSnapshot: jsonb('pricing_snapshot').notNull(), + note: varchar('note'), + startTime: time('start_time'), // nullable — only for hourly bookings + endTime: time('end_time'), // nullable — only for hourly bookings + createdAt: timestamp('created_at').defaultNow(), + updatedAt: timestamp('updated_at').defaultNow(), +}); diff --git a/backend/src/models/chatModel.js b/backend/src/models/chatModel.js new file mode 100644 index 0000000000..18382a4363 --- /dev/null +++ b/backend/src/models/chatModel.js @@ -0,0 +1,26 @@ +import { pgTable, uuid, text, boolean, timestamp, index, unique } from 'drizzle-orm/pg-core'; +import { usersTable } from './userModel.js'; +import { venuesTable } from './venueModel.js'; + +export const conversationsTable = pgTable('conversations', { + id: uuid('id').primaryKey().defaultRandom(), + userId: uuid('user_id').notNull().references(() => usersTable.id), + ownerId: uuid('owner_id').notNull().references(() => usersTable.id), + lastMessageAt: timestamp('last_message_at').defaultNow(), + createdAt: timestamp('created_at').defaultNow(), +}, (table) => ({ + uniqueParticipants: unique().on(table.userId, table.ownerId), +})); + +export const messagesTable = pgTable('messages', { + id: uuid('id').primaryKey().defaultRandom(), + conversationId: uuid('conversation_id').notNull().references(() => conversationsTable.id), + senderId: uuid('sender_id').notNull().references(() => usersTable.id), + venueId: uuid('venue_id').references(() => venuesTable.id), // nullable + content: text('content').notNull(), + isRead: boolean('is_read').default(false), + createdAt: timestamp('created_at').defaultNow(), +}, (table) => ({ + conversationCreatedIdx: index('conversation_created_idx').on(table.conversationId, table.createdAt), + conversationReadIdx: index('conversation_read_idx').on(table.conversationId, table.isRead), +})); \ No newline at end of file diff --git a/backend/src/models/index.js b/backend/src/models/index.js new file mode 100644 index 0000000000..a7e3071243 --- /dev/null +++ b/backend/src/models/index.js @@ -0,0 +1,40 @@ +import { usersTable,userFavourites } from './userModel.js'; +import { venuesTable, venuePricing } from './venueModel.js'; +import { amenities, venueAmenities } from './amenityModel.js'; +import { + venueRelations, + venuePricingRelations, + venueAmenitiesRelations, + amenitiesRelations, + userFavouritesRelations, + usersRelations, + paymentsRelations, + bookingRelations +} from './relations.js'; +import {bookingsTable} from './bookingModel.js'; +import {paymentsTable} from './paymentModel.js'; +import {notificationTypeEnum,notificationsTable} from './notificationModel.js' +import { conversationsTable,messagesTable } from './chatModel.js'; + +export { + usersTable, + venuesTable, + venuePricing, + amenities, + venueAmenities, + venueRelations, + venuePricingRelations, + venueAmenitiesRelations, + amenitiesRelations, + userFavourites, + userFavouritesRelations, + bookingsTable, + paymentsTable, + usersRelations, + paymentsRelations, + bookingRelations, + notificationTypeEnum, + notificationsTable, + conversationsTable, + messagesTable +}; diff --git a/backend/src/models/notificationModel.js b/backend/src/models/notificationModel.js new file mode 100644 index 0000000000..86d7261a92 --- /dev/null +++ b/backend/src/models/notificationModel.js @@ -0,0 +1,23 @@ +import { + pgTable, + uuid, + timestamp, + jsonb, + boolean, + pgEnum, + index +} from 'drizzle-orm/pg-core'; +import { usersTable } from './userModel.js'; + +export const notificationTypeEnum = pgEnum('notification_type', ['BOOKING_CONFIRMED','VENUE_APPROVED','VENUE_REJECTED']) + +export const notificationsTable = pgTable('notification', { + id: uuid('id').primaryKey().defaultRandom(), + recipientId: uuid('rid').references(() => usersTable.id), + type: notificationTypeEnum('type').notNull(), + payload: jsonb('payload'), + isRead: boolean('isRead').default(false), + createdAt: timestamp('created_at').defaultNow(), +},(table) => ({ + recipientIsReadIdx: index('recipientIsReadIdx').on(table.recipientId, table.isRead) +})); diff --git a/backend/src/models/paymentModel.js b/backend/src/models/paymentModel.js new file mode 100644 index 0000000000..e08cab80fa --- /dev/null +++ b/backend/src/models/paymentModel.js @@ -0,0 +1,25 @@ +import { + pgTable, + varchar, + uuid, + text, + primaryKey, + date, + timestamp, + numeric, +} from 'drizzle-orm/pg-core'; +import { bookingsTable } from './bookingModel.js'; + +export const paymentsTable = pgTable('payments', { + id: uuid('id').primaryKey().defaultRandom(), + bookingId: uuid('booking_id') + .references(() => bookingsTable.id) + .notNull(), + amount: numeric('amount', { precision: 10, scale: 2 }).notNull(), + status: varchar('status').notNull().default('pending'), + paidAt: timestamp('paid_at'), // nullable — filled after verification + createdAt: timestamp('created_at').defaultNow(), + phonePeOrderId: varchar('phone_pe_order_id').notNull(), // your merchantOrderId + phonePeTransactionId: varchar('phone_pe_transaction_id'), // PhonePe's internal orderId + phonePeTransactionRef: varchar('phone_pe_transaction_ref'), // filled after payment verify +}); diff --git a/backend/src/models/relations.js b/backend/src/models/relations.js new file mode 100644 index 0000000000..087c93fadf --- /dev/null +++ b/backend/src/models/relations.js @@ -0,0 +1,79 @@ +import { relations } from 'drizzle-orm'; +import { venuesTable, venuePricing } from './venueModel.js'; +import { venueAmenities, amenities } from './amenityModel.js'; +import { userFavourites, usersTable } from './userModel.js'; +import { bookingsTable } from './bookingModel.js'; +import { paymentsTable } from './paymentModel.js'; + +export const venueRelations = relations(venuesTable, ({ many,one }) => ({ + pricing: many(venuePricing), + venueAmenities: many(venueAmenities), + bookings: many(bookingsTable), + owner: one(usersTable, { + fields: [venuesTable.ownerId], + references: [usersTable.id], + }), +})); + +export const venuePricingRelations = relations(venuePricing, ({ one }) => ({ + venue: one(venuesTable, { + fields: [venuePricing.venueId], + references: [venuesTable.id], + }), +})); + +export const venueAmenitiesRelations = relations(venueAmenities, ({ one }) => ({ + venue: one(venuesTable, { + fields: [venueAmenities.venueId], + references: [venuesTable.id], + }), + amenity: one(amenities, { + fields: [venueAmenities.amenityId], + references: [amenities.id], + }), +})); + +export const amenitiesRelations = relations(amenities, ({ many }) => ({ + venueAmenities: many(venueAmenities), +})); + +export const userFavouritesRelations = relations(userFavourites, ({ one }) => ({ + venue: one(venuesTable, { + fields: [userFavourites.venueId], + references: [venuesTable.id], + }), + user: one(usersTable, { + fields: [userFavourites.userId], + references: [usersTable.id], + }), +})); + +export const bookingRelations = relations(bookingsTable, ({ one }) => ({ + venue: one(venuesTable, { + fields: [bookingsTable.venueId], + references: [venuesTable.id], + }), + + booker: one(usersTable, { + fields: [bookingsTable.bookerId], + references: [usersTable.id], + }), + + payment: one(paymentsTable, { + fields: [bookingsTable.id], + references: [paymentsTable.bookingId], + }), +})); + +export const paymentsRelations = relations(paymentsTable, ({ one }) => ({ + booking: one(bookingsTable, { + fields: [paymentsTable.bookingId], // FK on THIS table + references: [bookingsTable.id], // PK on the OTHER table + }), +})); + +export const usersRelations = relations(usersTable, ({ many }) => ({ + bookings: many(bookingsTable), + favourites: many(userFavourites), + venues: many(venuesTable) +})); diff --git a/backend/src/models/userModel.js b/backend/src/models/userModel.js new file mode 100644 index 0000000000..98dd474c19 --- /dev/null +++ b/backend/src/models/userModel.js @@ -0,0 +1,21 @@ +import { pgTable, varchar, uuid, text,primaryKey } from 'drizzle-orm/pg-core'; +import {venuesTable} from './venueModel.js' + +export const usersTable = pgTable('users', { + id: uuid().primaryKey().defaultRandom(), + username: varchar({ length: 255 }).notNull(), + email: varchar({ length: 255 }).notNull().unique(), + password: varchar({ length: 255 }).notNull(), + role: varchar({ length: 50 }).notNull().default('user'), // Add role field with default value + salt: text().notNull(), // Add salt if your hashPassword utility generates it! +}); + + +export const userFavourites = pgTable('user_favourites',{ + userId: uuid('user_id').references(() => usersTable.id).notNull(), + venueId: uuid('venue_id').references(() => venuesTable.id ).notNull()}, + (table) => ({ + pk: primaryKey({ columns: [table.userId, table.venueId] }), + }) + +) diff --git a/backend/src/models/venueModel.js b/backend/src/models/venueModel.js new file mode 100644 index 0000000000..c2381bc785 --- /dev/null +++ b/backend/src/models/venueModel.js @@ -0,0 +1,53 @@ +import { + pgTable, + varchar, + uuid, + text, + decimal, + integer, + jsonb, + boolean, + timestamp, +} from 'drizzle-orm/pg-core'; +import { usersTable } from './userModel.js'; + +export const venuesTable = pgTable('venues', { + id: uuid('id').primaryKey().defaultRandom(), + ownerId: uuid('owner_id') + .references(() => usersTable.id) + .notNull(), + name: varchar('name', { length: 255 }).notNull(), + description: text('description').notNull(), + type: varchar('type', { length: 100 }).notNull(), + address: text('address').notNull(), + city: varchar('city', { length: 100 }).notNull(), + state: varchar('state', { length: 100 }).notNull(), + pincode: varchar('pincode', { length: 20 }), + latitude: decimal('latitude', { precision: 10, scale: 7 }), + longitude: decimal('longitude', { precision: 10, scale: 7 }), + capacity: integer('capacity').notNull(), + images: jsonb('images').default([]), + openDays: jsonb('open_days').default([]), + openTime: varchar('open_time', { length: 5 }), + closeTime: varchar('close_time', { length: 5 }), + minBookingHours: integer('min_booking_hours').default(1), + isActive: boolean('is_active').notNull().default(true), + approvalStatus: varchar('approval_status', { length: 20 }).notNull().default('pending'), + adminNote: text('admin_note'), + bookingType: varchar('booking_type').notNull().default('daily'), // 'hourly' | 'daily' + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +export const venuePricing = pgTable('venue_pricing', { + id: uuid('id').defaultRandom().primaryKey(), + venueId: uuid('venue_id') + .references(() => venuesTable.id) + .notNull(), + dayType: varchar('day_type', { length: 20 }).notNull(), // weekday / weekend / holiday + price: decimal('price', { precision: 10, scale: 2 }).notNull(), + minHours: integer('min_hours').notNull().default(1), + validFrom: timestamp('valid_from').defaultNow().notNull(), + validTo: timestamp('valid_to'), // null = currently active + +}); diff --git a/backend/src/routes/adminRouter.js b/backend/src/routes/adminRouter.js new file mode 100644 index 0000000000..9a21c1673e --- /dev/null +++ b/backend/src/routes/adminRouter.js @@ -0,0 +1,53 @@ +import { Router } from 'express'; +import venueController from '../controllers/venueController.js'; +import { catchErrors } from '../handlers/error_handlers.js'; +import { isAuthenticated } from '../middlewares/authentication.js'; +import { requireRole } from '../middlewares/authentication.js'; +import analyticController from '../controllers/analyticController.js'; + +const router = Router(); +console.log('Venue router loaded'); + +router.get( + '/venues/pending', + isAuthenticated, + requireRole('admin'), + catchErrors(venueController.getPendingVenues) +); + +router.patch( + '/venues/:id/approve', + isAuthenticated, + requireRole('admin'), + catchErrors(venueController.approveVenue) +); + +router.patch( + '/venues/:id/reject', + isAuthenticated, + requireRole('admin'), + catchErrors(venueController.rejectVenue) +); + +router.patch( + '/:id/deactivate', + isAuthenticated, + requireRole('admin', 'owner'), + catchErrors(venueController.deactivateVenue) +); + +router.patch( + '/:id/activate', + isAuthenticated, + requireRole('admin', 'owner'), + catchErrors(venueController.activateVenue) +); + +router.get( + '/dashboard/stats', + isAuthenticated, + requireRole('admin'), + catchErrors(analyticController.adminDashboardStats) +); + +export default router; diff --git a/backend/src/routes/authRouter.js b/backend/src/routes/authRouter.js new file mode 100644 index 0000000000..0f47f7c1ce --- /dev/null +++ b/backend/src/routes/authRouter.js @@ -0,0 +1,17 @@ +import { Router } from 'express'; +import authController from '../controllers/authController.js'; +import { catchErrors } from '../handlers/error_handlers.js'; +import { isAuthenticated } from '../middlewares/authentication.js'; + +const router = Router(); +console.log('Routes file loaded'); + +router.post('/login', catchErrors(authController.login)); +router.post('/logout', catchErrors(authController.logout) ) +router.post('/admin/login',catchErrors(authController.login) ) + +router.post('/register', catchErrors(authController.register)); + +router.get('/me', isAuthenticated, catchErrors(authController.getCurrentUser)); + +export default router; diff --git a/backend/src/routes/bookingsRouter.js b/backend/src/routes/bookingsRouter.js new file mode 100644 index 0000000000..8c37afe56c --- /dev/null +++ b/backend/src/routes/bookingsRouter.js @@ -0,0 +1,44 @@ +import { Router } from 'express'; +import { catchErrors } from '../handlers/error_handlers.js'; +import { isAuthenticated } from '../middlewares/authentication.js'; +import { requireRole } from '../middlewares/authentication.js'; +import bookingController from '../controllers/bookingController.js'; + +const router = Router(); + +router.get( + '/venue/:id/availability', + isAuthenticated, + requireRole('user'), + catchErrors(bookingController.checkAvailability) +); +router.post( + '/bookings', + isAuthenticated, + requireRole('user'), + catchErrors(bookingController.bookVenue) +); + +router.get( + '/payments/verify/:bookingId', + isAuthenticated, + requireRole('user'), + catchErrors(bookingController.verifyPayment) +); + +router.get( + '/bookings/:userId', + isAuthenticated, + requireRole('user'), + catchErrors(bookingController.getUserBookings) +); + +router.get( + '/bookings/owner/:ownerId', + isAuthenticated, + requireRole('owner'), + catchErrors(bookingController.getOwnerBookings) +); + + +export default router; diff --git a/backend/src/routes/conversationRouter.js b/backend/src/routes/conversationRouter.js new file mode 100644 index 0000000000..ad9bf9eaa1 --- /dev/null +++ b/backend/src/routes/conversationRouter.js @@ -0,0 +1,11 @@ +import express from 'express'; +import conversationController from '../controllers/conversationController.js'; +import {isAuthenticated} from '../middlewares/authentication.js'; + +const router = express.Router(); + +router.post('/conversations/find-or-create', isAuthenticated, conversationController.findOrCreate); +router.get('/conversations/:id/messages', isAuthenticated, conversationController.getMessages); +router.get('/conversations', isAuthenticated, conversationController.getConversations); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/favouritesRouter.js b/backend/src/routes/favouritesRouter.js new file mode 100644 index 0000000000..5632b1a45b --- /dev/null +++ b/backend/src/routes/favouritesRouter.js @@ -0,0 +1,30 @@ +import { Router } from 'express'; +import favoriteController from '../controllers/favouriteController.js'; +import { catchErrors } from '../handlers/error_handlers.js'; +import { isAuthenticated } from '../middlewares/authentication.js'; +import { requireRole } from '../middlewares/authentication.js'; + +const router = Router(); + +router.post( + '/favorites/:venueId', + isAuthenticated, + requireRole('user'), + catchErrors(favoriteController.addFavorite) +); + +router.delete( + '/favorites/:venueId', + isAuthenticated, + requireRole('user'), + catchErrors(favoriteController.deleteFavorite) +); + +router.get( + '/favorites', + isAuthenticated, + requireRole('user'), + catchErrors(favoriteController.getFavorites) +); + +export default router; diff --git a/backend/src/routes/index.js b/backend/src/routes/index.js new file mode 100644 index 0000000000..40c23d77ff --- /dev/null +++ b/backend/src/routes/index.js @@ -0,0 +1,22 @@ +import { Router } from 'express'; +import authRouter from './authRouter.js'; +import venueRouter from './venueRouter.js'; +import favoriteRouter from './favouritesRouter.js'; +import bookingRouter from './bookingsRouter.js'; +import adminRouter from './adminRouter.js'; +import notificationRouter from './notificationRouter.js' +import conversationRouter from './conversationRouter.js'; + +const router = Router(); + +console.log('Routes file loaded'); + +router.use('/auth', authRouter); +router.use(venueRouter); +router.use(favoriteRouter); +router.use(bookingRouter); +router.use('/admin',adminRouter); +router.use(notificationRouter); +router.use(conversationRouter); + +export default router; diff --git a/backend/src/routes/notificationRouter.js b/backend/src/routes/notificationRouter.js new file mode 100644 index 0000000000..4473ae7463 --- /dev/null +++ b/backend/src/routes/notificationRouter.js @@ -0,0 +1,12 @@ +import { Router } from 'express'; +import notificationController from '../controllers/notificationController.js'; +import { catchErrors } from '../handlers/error_handlers.js'; +import { isAuthenticated } from '../middlewares/authentication.js'; + +const router = Router(); +console.log('Routes file loaded'); + +router.get('/notifications/stream',isAuthenticated, catchErrors(notificationController.setStream)) +router.patch('/notifications/read-all', isAuthenticated, catchErrors(notificationController.markAllRead)) + +export default router; diff --git a/backend/src/routes/venueRouter.js b/backend/src/routes/venueRouter.js new file mode 100644 index 0000000000..d7ed254266 --- /dev/null +++ b/backend/src/routes/venueRouter.js @@ -0,0 +1,48 @@ +import { Router } from 'express'; +import venueController from '../controllers/venueController.js'; +import { catchErrors } from '../handlers/error_handlers.js'; +import { isAuthenticated } from '../middlewares/authentication.js'; +import { requireRole } from '../middlewares/authentication.js'; + +const router = Router(); +console.log('Venue router loaded'); + +router.post( + '/venues', + isAuthenticated, + requireRole('owner'), + catchErrors(venueController.addVenue) +); + +router.patch( + '/venues/:id', + isAuthenticated, + requireRole('owner','admin'), + catchErrors(venueController.updateVenue) +) + +router.get( + '/owner/venues', + isAuthenticated, + requireRole('owner'), + catchErrors(venueController.getOwnerVenues) +); + +//users routes +router.get('/venues', isAuthenticated, requireRole('user', 'admin'), catchErrors(venueController.getVenues)); +router.get('/venue/:id', catchErrors(venueController.getVenueDetails)); + +router.patch( + '/owner/venue/:id/submit', + isAuthenticated, + requireRole('owner'), + catchErrors(venueController.checkSubmission) +); + +router.get( + '/amenities', + isAuthenticated, + catchErrors(venueController.getAmenities) +) + +export default router; diff --git a/backend/src/seeds/data/amenities.js b/backend/src/seeds/data/amenities.js new file mode 100644 index 0000000000..417e9b2af8 --- /dev/null +++ b/backend/src/seeds/data/amenities.js @@ -0,0 +1,77 @@ +export const amenitiesData = [ + // connectivity + { name: 'Wi-Fi', slug: 'wifi', icon: 'ti-wifi', category: 'connectivity' }, + { name: 'High-Speed Internet', slug: 'high_speed_internet', icon: 'ti-network', category: 'connectivity' }, + { name: 'Mobile Signal', slug: 'mobile_signal', icon: 'ti-mobile', category: 'connectivity' }, + { name: 'Bluetooth Speaker', slug: 'bluetooth_speaker', icon: 'ti-music', category: 'connectivity' }, + + // facilities + { name: 'Parking', slug: 'parking', icon: 'ti-car', category: 'facilities' }, + { name: 'Valet Parking', slug: 'valet_parking', icon: 'ti-car', category: 'facilities' }, + { name: 'AC', slug: 'ac', icon: 'ti-snowflake', category: 'facilities' }, + { name: 'Heating', slug: 'heating', icon: 'ti-thermometer', category: 'facilities' }, + { name: 'Kitchen', slug: 'kitchen', icon: 'ti-tools-kitchen-2', category: 'facilities' }, + { name: 'Private Restrooms', slug: 'private_restrooms', icon: 'ti-toilet', category: 'facilities' }, + { name: 'Shared Restrooms', slug: 'shared_restrooms', icon: 'ti-toilet', category: 'facilities' }, + { name: 'Wheelchair Accessible', slug: 'wheelchair_accessible', icon: 'ti-wheelchair', category: 'facilities' }, + { name: 'Elevator', slug: 'elevator', icon: 'ti-arrow-up', category: 'facilities' }, + { name: 'Storage Space', slug: 'storage_space', icon: 'ti-archive', category: 'facilities' }, + { name: 'Changing Rooms', slug: 'changing_rooms', icon: 'ti-id-badge', category: 'facilities' }, + { name: 'Outdoor Seating', slug: 'outdoor_seating', icon: 'ti-armchair', category: 'facilities' }, + { name: 'Indoor Seating', slug: 'indoor_seating', icon: 'ti-layers', category: 'facilities' }, + { name: 'Smoking Area', slug: 'smoking_area', icon: 'ti-smoke', category: 'facilities' }, + { name: 'Non-Smoking Area', slug: 'non_smoking_area', icon: 'ti-ban', category: 'facilities' }, + { name: 'Baby Changing Station', slug: 'baby_changing_station', icon: 'ti-baby-carriage', category: 'facilities' }, + { name: 'Pet Friendly', slug: 'pet_friendly', icon: 'ti-paw', category: 'facilities' }, + + // equipment + { name: 'Projector', slug: 'projector', icon: 'ti-device-projector', category: 'equipment' }, + { name: 'AV Equipment', slug: 'av_equipment', icon: 'ti-speakerphone', category: 'equipment' }, + { name: 'Microphones', slug: 'microphones', icon: 'ti-microphone', category: 'equipment' }, + { name: 'Sound System', slug: 'sound_system', icon: 'ti-headphone', category: 'equipment' }, + { name: 'Stage', slug: 'stage', icon: 'ti-device-tv', category: 'equipment' }, + { name: 'Lighting', slug: 'lighting', icon: 'ti-light-bulb', category: 'equipment' }, + { name: 'DJ Booth', slug: 'dj_booth', icon: 'ti-music-alt', category: 'equipment' }, + { name: 'Dance Floor', slug: 'dance_floor', icon: 'ti-music', category: 'equipment' }, + { name: 'Tables', slug: 'tables', icon: 'ti-table', category: 'equipment' }, + { name: 'Chairs', slug: 'chairs', icon: 'ti-chair', category: 'equipment' }, + { name: 'Whiteboard', slug: 'whiteboard', icon: 'ti-writing', category: 'equipment' }, + { name: 'Flip Chart', slug: 'flip_chart', icon: 'ti-file', category: 'equipment' }, + { name: 'Green Room', slug: 'green_room', icon: 'ti-door', category: 'equipment' }, + { name: 'AV Technician Available', slug: 'av_technician', icon: 'ti-user-check', category: 'equipment' }, + + // ambience + { name: 'Natural Light', slug: 'natural_light', icon: 'ti-sun', category: 'ambience' }, + { name: 'Ambient Lighting', slug: 'ambient_lighting', icon: 'ti-sun', category: 'ambience' }, + { name: 'Decor Included', slug: 'decor_included', icon: 'ti-paint-bucket', category: 'ambience' }, + { name: 'Catering', slug: 'catering', icon: 'ti-salad', category: 'ambience' }, + { name: 'Bar Service', slug: 'bar_service', icon: 'ti-glass', category: 'ambience' }, + { name: 'Lounge Area', slug: 'lounge_area', icon: 'ti-crown', category: 'ambience' }, + { name: 'Garden', slug: 'garden', icon: 'ti-leaf', category: 'ambience' }, + { name: 'Pool Access', slug: 'pool_access', icon: 'ti-drop', category: 'ambience' }, + { name: 'Terrace', slug: 'terrace', icon: 'ti-alarm-clock', category: 'ambience' }, + + // services + { name: 'Cleaning Service', slug: 'cleaning_service', icon: 'ti-brush-alt', category: 'services' }, + { name: 'Event Planner', slug: 'event_planner', icon: 'ti-calendar', category: 'services' }, + { name: 'On-site Staff', slug: 'on_site_staff', icon: 'ti-user', category: 'services' }, + { name: 'Security Staff', slug: 'security_staff', icon: 'ti-shield', category: 'services' }, + { name: 'Catering Staff', slug: 'catering_staff', icon: 'ti-user-check', category: 'services' }, + { name: 'Decoration Service', slug: 'decoration_service', icon: 'ti-heart', category: 'services' }, + { name: 'Wait Staff', slug: 'wait_staff', icon: 'ti-hand-stop', category: 'services' }, + { name: 'Parking Attendant', slug: 'parking_attendant', icon: 'ti-car', category: 'services' }, + { name: 'Technical Support', slug: 'technical_support', icon: 'ti-headphone-alt', category: 'services' }, + + // safety + { name: 'Security', slug: 'security', icon: 'ti-shield', category: 'safety' }, + { name: 'Fire Extinguishers', slug: 'fire_extinguishers', icon: 'ti-flame', category: 'safety' }, + { name: 'First Aid Kit', slug: 'first_aid_kit', icon: 'ti-medical', category: 'safety' }, + { name: 'Smoke Detectors', slug: 'smoke_detectors', icon: 'ti-alarm', category: 'safety' }, + { name: 'Emergency Exits', slug: 'emergency_exits', icon: 'ti-direction', category: 'safety' }, + + // hygiene + { name: 'Sanitizer Stations', slug: 'sanitizer_stations', icon: 'ti-droplet', category: 'hygiene' }, + { name: 'Hand Wash Stations', slug: 'hand_wash_stations', icon: 'ti-brush', category: 'hygiene' }, + { name: 'Daily Cleaning', slug: 'daily_cleaning', icon: 'ti-calendar-check', category: 'hygiene' }, + { name: 'Waste Disposal', slug: 'waste_disposal', icon: 'ti-trash', category: 'hygiene' }, +]; diff --git a/backend/src/seeds/data/users.js b/backend/src/seeds/data/users.js new file mode 100644 index 0000000000..c6e5bded0b --- /dev/null +++ b/backend/src/seeds/data/users.js @@ -0,0 +1,50 @@ +export const usersData = [ + { + id: '550e8400-e29b-41d4-a716-446655440001', + username: 'john_owner', + email: 'john@example.com', + password: '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86E36gZvQOm', + role: 'owner', + salt: 'salt1', + }, + { + id: '550e8400-e29b-41d4-a716-446655440002', + username: 'jane_owner', + email: 'jane@example.com', + password: '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86E36gZvQOm', + role: 'owner', + salt: 'salt2', + }, + { + id: '550e8400-e29b-41d4-a716-446655440003', + username: 'alex_user', + email: 'alex@example.com', + password: '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86E36gZvQOm', + role: 'user', + salt: 'salt3', + }, + { + id: '550e8400-e29b-41d4-a716-446655440004', + username: 'sarah_user', + email: 'sarah@example.com', + password: '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86E36gZvQOm', + role: 'user', + salt: 'salt4', + }, + { + id: '550e8400-e29b-41d4-a716-446655440005', + username: 'michael_user', + email: 'michael@example.com', + password: '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86E36gZvQOm', + role: 'user', + salt: 'salt5', + }, + { + id: '550e8400-e29b-41d4-a716-446655440006', + username: 'admin_user', + email: 'admin@example.com', + password: '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86E36gZvQOm', + role: 'admin', + salt: 'salt6', + }, +]; diff --git a/backend/src/seeds/data/venueAmenities.js b/backend/src/seeds/data/venueAmenities.js new file mode 100644 index 0000000000..ad175a2180 --- /dev/null +++ b/backend/src/seeds/data/venueAmenities.js @@ -0,0 +1,81 @@ +export const venueAmenitiesData = [ + // Grand Ballroom amenities + { + venueId: '550e8400-e29b-41d4-a716-446655440010', // Grand Ballroom + amenityId: '550e8400-e29b-41d4-a716-446655440101', // Wi-Fi + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + amenityId: '550e8400-e29b-41d4-a716-446655440102', // Parking + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + amenityId: '550e8400-e29b-41d4-a716-446655440103', // AC + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + amenityId: '550e8400-e29b-41d4-a716-446655440104', // Kitchen + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + amenityId: '550e8400-e29b-41d4-a716-446655440106', // Security + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + amenityId: '550e8400-e29b-41d4-a716-446655440108', // Projector + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + amenityId: '550e8400-e29b-41d4-a716-446655440110', // Stage + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + amenityId: '550e8400-e29b-41d4-a716-446655440113', // Catering + }, + // Tech Hub Meeting Rooms amenities + { + venueId: '550e8400-e29b-41d4-a716-446655440011', // Tech Hub + amenityId: '550e8400-e29b-41d4-a716-446655440101', // Wi-Fi + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + amenityId: '550e8400-e29b-41d4-a716-446655440103', // AC + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + amenityId: '550e8400-e29b-41d4-a716-446655440108', // Projector + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + amenityId: '550e8400-e29b-41d4-a716-446655440109', // AV Equipment + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + amenityId: '550e8400-e29b-41d4-a716-446655440110', // Whiteboard + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + amenityId: '550e8400-e29b-41d4-a716-446655440105', // Wheelchair Accessible + }, + // Garden Bistro amenities + { + venueId: '550e8400-e29b-41d4-a716-446655440012', // Garden Bistro + amenityId: '550e8400-e29b-41d4-a716-446655440101', // Wi-Fi + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + amenityId: '550e8400-e29b-41d4-a716-446655440102', // Parking + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + amenityId: '550e8400-e29b-41d4-a716-446655440107', // Outdoor Seating + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + amenityId: '550e8400-e29b-41d4-a716-446655440112', // Natural Light + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + amenityId: '550e8400-e29b-41d4-a716-446655440113', // Catering + }, +]; diff --git a/backend/src/seeds/data/venuePricing.js b/backend/src/seeds/data/venuePricing.js new file mode 100644 index 0000000000..b3e6ad7160 --- /dev/null +++ b/backend/src/seeds/data/venuePricing.js @@ -0,0 +1,112 @@ +export const venuePricingData = [ + // Grand Ballroom pricing (daily) + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + dayType: 'weekday', + price: '35000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + dayType: 'weekend', + price: '50000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + dayType: 'holiday', + price: '60000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Tech Hub Meeting Rooms pricing (hourly) + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + dayType: 'weekday', + price: '600.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + dayType: 'weekend', + price: '900.00', + minHours: 2, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Garden Bistro pricing (daily) + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + dayType: 'weekday', + price: '15000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + dayType: 'weekend', + price: '25000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Cozy Workspace pricing (hourly) + { + venueId: '550e8400-e29b-41d4-a716-446655440013', + dayType: 'weekday', + price: '150.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440013', + dayType: 'weekend', + price: '250.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Penthouse Suite pricing (daily) + { + venueId: '550e8400-e29b-41d4-a716-446655440014', + dayType: 'weekday', + price: '40000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440014', + dayType: 'weekend', + price: '55000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Rusty Shed pricing (hourly) + { + venueId: '550e8400-e29b-41d4-a716-446655440015', + dayType: 'weekday', + price: '100.00', + minHours: 2, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440015', + dayType: 'weekend', + price: '150.00', + minHours: 2, + validFrom: new Date('2024-01-01'), + validTo: null, + }, +]; diff --git a/backend/src/seeds/data/venues.js b/backend/src/seeds/data/venues.js new file mode 100644 index 0000000000..377dbaa7c8 --- /dev/null +++ b/backend/src/seeds/data/venues.js @@ -0,0 +1,154 @@ +export const venuesData = [ + { + id: '550e8400-e29b-41d4-a716-446655440010', + ownerId: '550e8400-e29b-41d4-a716-446655440001', + name: 'Grand Ballroom', + description: 'Elegant ballroom perfect for weddings and corporate events', + type: 'banquet_hall', + address: '123 Main Street, Downtown', + city: 'Mumbai', + state: 'Maharashtra', + pincode: '400001', + latitude: '19.0760', + longitude: '72.8777', + capacity: 500, + images: [ + { url: 'https://example.com/ballroom1.jpg', alt: 'Main hall' }, + { url: 'https://example.com/ballroom2.jpg', alt: 'Decorated hall' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], + openTime: '10:00', + closeTime: '23:00', + minBookingHours: 4, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440011', + ownerId: '550e8400-e29b-41d4-a716-446655440002', + name: 'Tech Hub Meeting Rooms', + description: 'Modern meeting spaces with latest technology', + type: 'meeting_room', + address: '456 Tech Park, Bandra', + city: 'Mumbai', + state: 'Maharashtra', + pincode: '400051', + latitude: '19.0596', + longitude: '72.8295', + capacity: 50, + images: [ + { url: 'https://example.com/office1.jpg', alt: 'Meeting room' }, + { url: 'https://example.com/office2.jpg', alt: 'Conference setup' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], + openTime: '09:00', + closeTime: '18:00', + minBookingHours: 1, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'hourly', + }, + { + id: '550e8400-e29b-41d4-a716-446655440012', + ownerId: '550e8400-e29b-41d4-a716-446655440001', + name: 'Garden Bistro', + description: 'Outdoor garden venue perfect for intimate gatherings', + type: 'outdoor_space', + address: '789 Garden Lane, Andheri', + city: 'Mumbai', + state: 'Maharashtra', + pincode: '400072', + latitude: '19.1136', + longitude: '72.8697', + capacity: 100, + images: [ + { url: 'https://example.com/garden1.jpg', alt: 'Garden setup' }, + ], + openDays: ['Thursday', 'Friday', 'Saturday', 'Sunday'], + openTime: '17:00', + closeTime: '23:00', + minBookingHours: 3, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440013', + ownerId: '550e8400-e29b-41d4-a716-446655440002', + name: 'Cozy Workspace', + description: 'Quiet desk spaces with high-speed internet and coffee', + type: 'coworking_space', + address: '12 Rose Avenue, Koregaon Park', + city: 'Pune', + state: 'Maharashtra', + pincode: '411001', + latitude: '18.5362', + longitude: '73.8930', + capacity: 20, + images: [ + { url: 'https://example.com/cowork1.jpg', alt: 'Workspace area' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + openTime: '08:00', + closeTime: '20:00', + minBookingHours: 1, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'hourly', + }, + { + id: '550e8400-e29b-41d4-a716-446655440014', + ownerId: '550e8400-e29b-41d4-a716-446655440001', + name: 'Penthouse Suite', + description: 'Luxury rooftop suite with stunning city skyline views', + type: 'party_lounge', + address: '99 Sky Tower, MG Road', + city: 'Bangalore', + state: 'Karnataka', + pincode: '560001', + latitude: '12.9716', + longitude: '77.5946', + capacity: 75, + images: [ + { url: 'https://example.com/penthouse.jpg', alt: 'Rooftop view' }, + ], + openDays: ['Friday', 'Saturday', 'Sunday'], + openTime: '16:00', + closeTime: '02:00', + minBookingHours: 5, + isActive: true, + approvalStatus: 'pending', + adminNote: 'Requires noise level check', + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440015', + ownerId: '550e8400-e29b-41d4-a716-446655440002', + name: 'Rusty Shed', + description: 'Old industrial shed suited for grungy photography shoots', + type: 'studio', + address: '7 Outer Ring Road', + city: 'Chennai', + state: 'Tamil Nadu', + pincode: '600001', + latitude: '13.0827', + longitude: '80.2707', + capacity: 30, + images: [ + { url: 'https://example.com/shed.jpg', alt: 'Industrial shed' }, + ], + openDays: ['Saturday', 'Sunday'], + openTime: '10:00', + closeTime: '18:00', + minBookingHours: 2, + isActive: false, + approvalStatus: 'rejected', + adminNote: 'Failed safety inspection due to structural issues', + bookingType: 'hourly', + }, +]; diff --git a/backend/src/seeds/index.js b/backend/src/seeds/index.js new file mode 100644 index 0000000000..df4f912987 --- /dev/null +++ b/backend/src/seeds/index.js @@ -0,0 +1,27 @@ +import { seedAmenities } from './seeders/01.amenities.seeder.js'; +import { seedUsers } from './seeders/02.users.seeder.js'; +import { seedVenues } from './seeders/03.venues.seeder.js'; +import { seedVenuePricing } from './seeders/04.venuePricing.seeder.js'; +import { seedVenueAmenities } from './seeders/05.venueAmenities.seeder.js'; +import { seedBookings } from './seeders/06.bookings.seeder.js'; +import { seedPayments } from './seeders/07.payments.seeder.js'; + +const runSeeds = async () => { + try { + console.log('🌱 Starting seed...\n'); + await seedAmenities(); + await seedUsers(); + await seedVenues(); + await seedVenuePricing(); + await seedVenueAmenities(); + await seedBookings(); + await seedPayments(); + console.log('\n✅ All seeds completed.'); + } catch (error) { + console.error('❌ Seed failed:', error); + } finally { + process.exit(0); + } +}; + +runSeeds(); diff --git a/backend/src/seeds/seeders/01.amenities.seeder.js b/backend/src/seeds/seeders/01.amenities.seeder.js new file mode 100644 index 0000000000..c554de6eef --- /dev/null +++ b/backend/src/seeds/seeders/01.amenities.seeder.js @@ -0,0 +1,9 @@ +import { db } from '../../db/index.js'; +import { amenities } from '../../models/amenityModel.js'; +import { amenitiesData } from '../data/amenities.js'; + +export const seedAmenities = async () => { + console.log('Seeding amenities...'); + await db.insert(amenities).values(amenitiesData).onConflictDoNothing(); + console.log('✓ Amenities done'); +}; diff --git a/backend/src/seeds/seeders/02.users.seeder.js b/backend/src/seeds/seeders/02.users.seeder.js new file mode 100644 index 0000000000..ba25e8d6a8 --- /dev/null +++ b/backend/src/seeds/seeders/02.users.seeder.js @@ -0,0 +1,9 @@ +import { db } from '../../db/index.js'; +import { usersTable } from '../../models/userModel.js'; +import { usersData } from '../data/users.js'; + +export const seedUsers = async () => { + console.log('Seeding users...'); + await db.insert(usersTable).values(usersData).onConflictDoNothing(); + console.log('✓ Users done'); +}; diff --git a/backend/src/seeds/seeders/03.venues.seeder.js b/backend/src/seeds/seeders/03.venues.seeder.js new file mode 100644 index 0000000000..3a775f931f --- /dev/null +++ b/backend/src/seeds/seeders/03.venues.seeder.js @@ -0,0 +1,308 @@ +import { db } from '../../db/index.js'; +import { venuesTable } from '../../models/venueModel.js'; +import { usersTable } from '../../models/userModel.js'; +import { eq } from 'drizzle-orm'; + +export const seedVenues = async () => { + console.log('Seeding venues...'); + + // Get the actual user IDs from the database + const johnOwner = await db + .select({ id: usersTable.id }) + .from(usersTable) + .where(eq(usersTable.username, 'john_owner')) + .limit(1); + + const janeOwner = await db + .select({ id: usersTable.id }) + .from(usersTable) + .where(eq(usersTable.username, 'jane_owner')) + .limit(1); + + if (!johnOwner[0] || !janeOwner[0]) { + throw new Error('Users not found. Please seed users first.'); + } + + const venuesData = [ + { + id: '550e8400-e29b-41d4-a716-446655440010', + ownerId: johnOwner[0].id, + name: 'Grand Ballroom', + description: 'Elegant ballroom perfect for weddings and corporate events', + type: 'banquet_hall', + address: '123 Main Street, Downtown', + city: 'Mumbai', + state: 'Maharashtra', + pincode: '400001', + latitude: '19.0760', + longitude: '72.8777', + capacity: 500, + images: [ + { url: 'https://example.com/ballroom1.jpg', alt: 'Main hall' }, + { url: 'https://example.com/ballroom2.jpg', alt: 'Decorated hall' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], + openTime: '10:00', + closeTime: '23:00', + minBookingHours: 4, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440011', + ownerId: janeOwner[0].id, + name: 'Tech Hub Meeting Rooms', + description: 'Modern meeting spaces with latest technology', + type: 'meeting_room', + address: '456 Tech Park, Bandra', + city: 'Mumbai', + state: 'Maharashtra', + pincode: '400051', + latitude: '19.0596', + longitude: '72.8295', + capacity: 50, + images: [ + { url: 'https://example.com/office1.jpg', alt: 'Meeting room' }, + { url: 'https://example.com/office2.jpg', alt: 'Conference setup' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], + openTime: '09:00', + closeTime: '18:00', + minBookingHours: 1, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'hourly', + }, + { + id: '550e8400-e29b-41d4-a716-446655440012', + ownerId: johnOwner[0].id, + name: 'Garden Bistro', + description: 'Outdoor garden venue perfect for intimate gatherings', + type: 'outdoor_space', + address: '789 Garden Lane, Andheri', + city: 'Mumbai', + state: 'Maharashtra', + pincode: '400072', + latitude: '19.1136', + longitude: '72.8697', + capacity: 100, + images: [ + { url: 'https://example.com/garden1.jpg', alt: 'Garden setup' }, + ], + openDays: ['Thursday', 'Friday', 'Saturday', 'Sunday'], + openTime: '17:00', + closeTime: '23:00', + minBookingHours: 3, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440013', + ownerId: janeOwner[0].id, + name: 'Cozy Workspace', + description: 'Quiet desk spaces with high-speed internet and coffee', + type: 'coworking_space', + address: '12 Rose Avenue, Koregaon Park', + city: 'Pune', + state: 'Maharashtra', + pincode: '411001', + latitude: '18.5362', + longitude: '73.8930', + capacity: 20, + images: [ + { url: 'https://example.com/cowork1.jpg', alt: 'Workspace area' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + openTime: '08:00', + closeTime: '20:00', + minBookingHours: 1, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'hourly', + }, + { + id: '550e8400-e29b-41d4-a716-446655440014', + ownerId: johnOwner[0].id, + name: 'Penthouse Suite', + description: 'Luxury rooftop suite with stunning city skyline views', + type: 'party_lounge', + address: '99 Sky Tower, MG Road', + city: 'Bangalore', + state: 'Karnataka', + pincode: '560001', + latitude: '12.9716', + longitude: '77.5946', + capacity: 75, + images: [ + { url: 'https://example.com/penthouse.jpg', alt: 'Rooftop view' }, + ], + openDays: ['Friday', 'Saturday', 'Sunday'], + openTime: '16:00', + closeTime: '02:00', + minBookingHours: 5, + isActive: true, + approvalStatus: 'pending', + adminNote: 'Requires noise level check', + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440015', + ownerId: janeOwner[0].id, + name: 'Rusty Shed', + description: 'Old industrial shed suited for grungy photography shoots', + type: 'studio', + address: '7 Outer Ring Road', + city: 'Chennai', + state: 'Tamil Nadu', + pincode: '600001', + latitude: '13.0827', + longitude: '80.2707', + capacity: 30, + images: [ + { url: 'https://example.com/shed.jpg', alt: 'Industrial shed' }, + ], + openDays: ['Saturday', 'Sunday'], + openTime: '10:00', + closeTime: '18:00', + minBookingHours: 2, + isActive: false, + approvalStatus: 'rejected', + adminNote: 'Failed safety inspection due to structural issues', + bookingType: 'hourly', + }, + { + id: '550e8400-e29b-41d4-a716-446655440016', + ownerId: johnOwner[0].id, + name: 'Lakeside Pavilion', + description: 'Scenic pavilion by the lake, ideal for evening parties and receptions', + type: 'outdoor_space', + address: '22 Lake View Road', + city: 'Pune', + state: 'Maharashtra', + pincode: '411042', + latitude: '18.5150', + longitude: '73.8562', + capacity: 250, + images: [ + { url: 'https://example.com/lakeside1.jpg', alt: 'Pavilion by the lake' }, + ], + openDays: ['Thursday', 'Friday', 'Saturday', 'Sunday'], + openTime: '16:00', + closeTime: '01:00', + minBookingHours: 4, + isActive: true, + approvalStatus: 'pending', + adminNote: 'Pending review of parking arrangements', + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440017', + ownerId: janeOwner[0].id, + name: 'Urban Art Loft', + description: 'Creative loft space designed for gallery openings and workshops', + type: 'event_space', + address: '101 Creative Street', + city: 'Bangalore', + state: 'Karnataka', + pincode: '560034', + latitude: '12.9718', + longitude: '77.6413', + capacity: 120, + images: [ + { url: 'https://example.com/loft.jpg', alt: 'Art loft interior' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + openTime: '10:00', + closeTime: '22:00', + minBookingHours: 2, + isActive: true, + approvalStatus: 'approved', + adminNote: null, + bookingType: 'hourly', + }, + { + id: '550e8400-e29b-41d4-a716-446655440018', + ownerId: johnOwner[0].id, + name: 'Heritage Courtyard', + description: 'Historic courtyard venue with classic architecture and charm', + type: 'heritage_property', + address: '88 Heritage Lane', + city: 'Jaipur', + state: 'Rajasthan', + pincode: '302001', + latitude: '26.9124', + longitude: '75.7873', + capacity: 180, + images: [ + { url: 'https://example.com/courtyard.jpg', alt: 'Courtyard view' }, + ], + openDays: ['Friday', 'Saturday', 'Sunday'], + openTime: '14:00', + closeTime: '23:00', + minBookingHours: 4, + isActive: true, + approvalStatus: 'rejected', + adminNote: 'No fire safety certificate on file', + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440019', + ownerId: janeOwner[0].id, + name: 'Sunrise Conference Center', + description: 'Large conference center with multiple halls and breakout rooms', + type: 'conference_center', + address: '77 Business Park', + city: 'Hyderabad', + state: 'Telangana', + pincode: '500032', + latitude: '17.3850', + longitude: '78.4867', + capacity: 400, + images: [ + { url: 'https://example.com/conference.jpg', alt: 'Conference hall' }, + ], + openDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], + openTime: '08:00', + closeTime: '20:00', + minBookingHours: 3, + isActive: false, + approvalStatus: 'approved', + adminNote: 'Temporarily inactive for renovation', + bookingType: 'daily', + }, + { + id: '550e8400-e29b-41d4-a716-446655440020', + ownerId: johnOwner[0].id, + name: 'Beachside Banquet', + description: 'Open-air banquet venue right by the beach', + type: 'banquet_hall', + address: '12 Shoreline Road', + city: 'Goa', + state: 'Goa', + pincode: '403001', + latitude: '15.4968', + longitude: '73.8278', + capacity: 300, + images: [ + { url: 'https://example.com/beachbanquet.jpg', alt: 'Beachside banquet' }, + ], + openDays: ['Friday', 'Saturday', 'Sunday'], + openTime: '15:00', + closeTime: '01:00', + minBookingHours: 5, + isActive: true, + approvalStatus: 'pending', + adminNote: 'Pending review of flood safety measures', + bookingType: 'daily', + }, + ]; + + await db.insert(venuesTable).values(venuesData).onConflictDoNothing(); + console.log('✓ Venues done'); +}; diff --git a/backend/src/seeds/seeders/04.venuePricing.seeder.js b/backend/src/seeds/seeders/04.venuePricing.seeder.js new file mode 100644 index 0000000000..4f37714ca9 --- /dev/null +++ b/backend/src/seeds/seeders/04.venuePricing.seeder.js @@ -0,0 +1,122 @@ +import { db } from '../../db/index.js'; +import { venuePricing } from '../../models/venueModel.js'; + +export const seedVenuePricing = async () => { + console.log('Seeding venue pricing...'); + + const venuePricingData = [ + // Grand Ballroom pricing (daily) + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + dayType: 'weekday', + price: '35000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + dayType: 'weekend', + price: '50000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440010', + dayType: 'holiday', + price: '60000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Tech Hub Meeting Rooms pricing (hourly) + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + dayType: 'weekday', + price: '600.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440011', + dayType: 'weekend', + price: '900.00', + minHours: 2, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Garden Bistro pricing (daily) + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + dayType: 'weekday', + price: '15000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440012', + dayType: 'weekend', + price: '25000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Cozy Workspace pricing (hourly) + { + venueId: '550e8400-e29b-41d4-a716-446655440013', + dayType: 'weekday', + price: '150.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440013', + dayType: 'weekend', + price: '250.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Penthouse Suite pricing (daily) + { + venueId: '550e8400-e29b-41d4-a716-446655440014', + dayType: 'weekday', + price: '40000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440014', + dayType: 'weekend', + price: '55000.00', + minHours: 1, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + // Rusty Shed pricing (hourly) + { + venueId: '550e8400-e29b-41d4-a716-446655440015', + dayType: 'weekday', + price: '100.00', + minHours: 2, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + { + venueId: '550e8400-e29b-41d4-a716-446655440015', + dayType: 'weekend', + price: '150.00', + minHours: 2, + validFrom: new Date('2024-01-01'), + validTo: null, + }, + ]; + + await db.insert(venuePricing).values(venuePricingData).onConflictDoNothing(); + console.log('✓ Venue pricing done'); +}; diff --git a/backend/src/seeds/seeders/05.venueAmenities.seeder.js b/backend/src/seeds/seeders/05.venueAmenities.seeder.js new file mode 100644 index 0000000000..9ac95b704e --- /dev/null +++ b/backend/src/seeds/seeders/05.venueAmenities.seeder.js @@ -0,0 +1,61 @@ +import { db } from '../../db/index.js'; +import { venueAmenities, amenities } from '../../models/index.js'; +import { eq } from 'drizzle-orm'; + +export const seedVenueAmenities = async () => { + console.log('Seeding venue amenities...'); + + // Get all amenities and map by slug + const allAmenities = await db.select({ id: amenities.id, slug: amenities.slug }).from(amenities); + + if (allAmenities.length === 0) { + throw new Error('No amenities found. Please seed amenities first.'); + } + + const amenityMap = {}; + allAmenities.forEach((amenity) => { + amenityMap[amenity.slug] = amenity.id; + }); + + const venueAmenitiesData = [ + // Grand Ballroom amenities + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['wifi'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['parking'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['ac'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['kitchen'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['security'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['projector'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['stage'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440010', amenityId: amenityMap['catering'] }, + // Tech Hub Meeting Rooms amenities + { venueId: '550e8400-e29b-41d4-a716-446655440011', amenityId: amenityMap['wifi'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440011', amenityId: amenityMap['ac'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440011', amenityId: amenityMap['projector'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440011', amenityId: amenityMap['av_equipment'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440011', amenityId: amenityMap['whiteboard'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440011', amenityId: amenityMap['wheelchair'] }, + // Garden Bistro amenities + { venueId: '550e8400-e29b-41d4-a716-446655440012', amenityId: amenityMap['wifi'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440012', amenityId: amenityMap['parking'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440012', amenityId: amenityMap['outdoor_seating'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440012', amenityId: amenityMap['natural_light'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440012', amenityId: amenityMap['catering'] }, + // Cozy Workspace amenities + { venueId: '550e8400-e29b-41d4-a716-446655440013', amenityId: amenityMap['wifi'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440013', amenityId: amenityMap['ac'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440013', amenityId: amenityMap['parking'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440013', amenityId: amenityMap['wheelchair'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440013', amenityId: amenityMap['natural_light'] }, + // Penthouse Suite amenities + { venueId: '550e8400-e29b-41d4-a716-446655440014', amenityId: amenityMap['wifi'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440014', amenityId: amenityMap['ac'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440014', amenityId: amenityMap['security'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440014', amenityId: amenityMap['catering'] }, + { venueId: '550e8400-e29b-41d4-a716-446655440014', amenityId: amenityMap['outdoor_seating'] }, + // Rusty Shed amenities + { venueId: '550e8400-e29b-41d4-a716-446655440015', amenityId: amenityMap['natural_light'] }, + ]; + + await db.insert(venueAmenities).values(venueAmenitiesData).onConflictDoNothing(); + console.log('✓ Venue amenities done'); +}; diff --git a/backend/src/seeds/seeders/06.bookings.seeder.js b/backend/src/seeds/seeders/06.bookings.seeder.js new file mode 100644 index 0000000000..a127063120 --- /dev/null +++ b/backend/src/seeds/seeders/06.bookings.seeder.js @@ -0,0 +1,191 @@ +import { db } from '../../db/index.js'; +import { bookingsTable } from '../../models/bookingModel.js'; +import { usersTable } from '../../models/userModel.js'; +import { eq } from 'drizzle-orm'; + +export const seedBookings = async () => { + console.log('Seeding bookings...'); + + const alex = await db + .select({ id: usersTable.id }) + .from(usersTable) + .where(eq(usersTable.username, 'alex_user')) + .limit(1); + + const sarah = await db + .select({ id: usersTable.id }) + .from(usersTable) + .where(eq(usersTable.username, 'sarah_user')) + .limit(1); + + const michael = await db + .select({ id: usersTable.id }) + .from(usersTable) + .where(eq(usersTable.username, 'michael_user')) + .limit(1); + + if (!alex[0] || !sarah[0] || !michael[0]) { + throw new Error('Bookers not found. Please seed users first.'); + } + + const bookingsData = [ + // 1. Grand Ballroom - Approved Daily Booking (Alex) + // Dates: 2026-07-01 to 2026-07-02 (2 weekdays) + // Rate: Weekday = 35000.00. Total = 70000.00 + { + id: '550e8400-e29b-41d4-a716-446655440050', + venueId: '550e8400-e29b-41d4-a716-446655440010', + bookerId: alex[0].id, + startDate: '2026-07-01', + endDate: '2026-07-02', + status: 'approved', + totalAmount: '70000.00', + pricingSnapshot: { + bookingType: 'daily', + basePrice: '35000.00', + breakdown: [ + { date: '2026-07-01', dayType: 'weekday', price: '35000.00' }, + { date: '2026-07-02', dayType: 'weekday', price: '35000.00' } + ] + }, + note: 'Need wedding decorations setup on the evening before.', + startTime: null, + endTime: null, + }, + // 2. Tech Hub Meeting Rooms - Pending Hourly Booking (Sarah) + // Date: 2026-07-05 (Sunday - Weekend) + // Rate: Weekend = 900.00. Hours: 10:00 to 14:00 (4 hours). Total = 3600.00 + { + id: '550e8400-e29b-41d4-a716-446655440051', + venueId: '550e8400-e29b-41d4-a716-446655440011', + bookerId: sarah[0].id, + startDate: '2026-07-05', + endDate: '2026-07-05', + status: 'pending', + totalAmount: '3600.00', + pricingSnapshot: { + bookingType: 'hourly', + pricePerHour: '900.00', + hours: 4, + date: '2026-07-05', + dayType: 'weekend' + }, + note: 'Board meeting. Please ensure HDMI cables and whiteboard markers are available.', + startTime: '10:00:00', + endTime: '14:00:00', + }, + // 3. Garden Bistro - Cancelled Daily Booking (Alex) + // Dates: 2026-07-10 to 2026-07-11 (Friday = Weekday: 15000.00, Saturday = Weekend: 25000.00) + // Total = 40000.00 + { + id: '550e8400-e29b-41d4-a716-446655440052', + venueId: '550e8400-e29b-41d4-a716-446655440012', + bookerId: alex[0].id, + startDate: '2026-07-10', + endDate: '2026-07-11', + status: 'cancelled', + totalAmount: '40000.00', + pricingSnapshot: { + bookingType: 'daily', + breakdown: [ + { date: '2026-07-10', dayType: 'weekday', price: '15000.00' }, + { date: '2026-07-11', dayType: 'weekend', price: '25000.00' } + ] + }, + note: 'Birthday celebration. Cancelled due to weather forecast.', + startTime: null, + endTime: null, + }, + // 4. Cozy Workspace - Rejected Hourly Booking (Michael) + // Date: 2026-07-12 (Sunday - Weekend) + // Rate: Weekend = 250.00. Hours: 14:00 to 16:00 (2 hours). Total = 500.00 + { + id: '550e8400-e29b-41d4-a716-446655440053', + venueId: '550e8400-e29b-41d4-a716-446655440013', + bookerId: michael[0].id, + startDate: '2026-07-12', + endDate: '2026-07-12', + status: 'rejected', + totalAmount: '500.00', + pricingSnapshot: { + bookingType: 'hourly', + pricePerHour: '250.00', + hours: 2, + date: '2026-07-12', + dayType: 'weekend' + }, + note: 'Need workspace for exam prep. Rejected because the venue is booked for maintenance.', + startTime: '14:00:00', + endTime: '16:00:00', + }, + // 5. Grand Ballroom - Approved Daily Booking (Sarah) + // Dates: 2026-07-15 to 2026-07-15 (1 weekday) + // Rate: Weekday = 35000.00. Total = 35000.00 + { + id: '550e8400-e29b-41d4-a716-446655440054', + venueId: '550e8400-e29b-41d4-a716-446655440010', + bookerId: sarah[0].id, + startDate: '2026-07-15', + endDate: '2026-07-15', + status: 'approved', + totalAmount: '35000.00', + pricingSnapshot: { + bookingType: 'daily', + basePrice: '35000.00', + breakdown: [ + { date: '2026-07-15', dayType: 'weekday', price: '35000.00' } + ] + }, + note: 'Corporate workshop.', + startTime: null, + endTime: null, + }, + // 6. Cozy Workspace - Approved Hourly Booking (Michael) + // Date: 2026-07-14 (Tuesday - Weekday) + // Rate: Weekday = 150.00. Hours: 09:00 to 17:00 (8 hours). Total = 1200.00 + { + id: '550e8400-e29b-41d4-a716-446655440055', + venueId: '550e8400-e29b-41d4-a716-446655440013', + bookerId: michael[0].id, + startDate: '2026-07-14', + endDate: '2026-07-14', + status: 'approved', + totalAmount: '1200.00', + pricingSnapshot: { + bookingType: 'hourly', + pricePerHour: '150.00', + hours: 8, + date: '2026-07-14', + dayType: 'weekday' + }, + note: 'All-day programming sprint.', + startTime: '09:00:00', + endTime: '17:00:00', + }, + // 7. Tech Hub Meeting Rooms - Approved Hourly Booking (Alex) + // Date: 2026-07-16 (Thursday - Weekday) + // Rate: Weekday = 600.00. Hours: 14:00 to 17:00 (3 hours). Total = 1800.00 + { + id: '550e8400-e29b-41d4-a716-446655440056', + venueId: '550e8400-e29b-41d4-a716-446655440011', + bookerId: alex[0].id, + startDate: '2026-07-16', + endDate: '2026-07-16', + status: 'approved', + totalAmount: '1800.00', + pricingSnapshot: { + bookingType: 'hourly', + pricePerHour: '600.00', + hours: 3, + date: '2026-07-16', + dayType: 'weekday' + }, + note: 'Client presentation.', + startTime: '14:00:00', + endTime: '17:00:00', + } + ]; + + await db.insert(bookingsTable).values(bookingsData).onConflictDoNothing(); + console.log('✓ Bookings done'); +}; diff --git a/backend/src/seeds/seeders/07.payments.seeder.js b/backend/src/seeds/seeders/07.payments.seeder.js new file mode 100644 index 0000000000..f83debd9a6 --- /dev/null +++ b/backend/src/seeds/seeders/07.payments.seeder.js @@ -0,0 +1,89 @@ +import { db } from '../../db/index.js'; +import { paymentsTable } from '../../models/paymentModel.js'; + +export const seedPayments = async () => { + console.log('Seeding payments...'); + + const paymentsData = [ + // 1. Payment for Booking 1 (Completed) + { + id: '550e8400-e29b-41d4-a716-446655440070', + bookingId: '550e8400-e29b-41d4-a716-446655440050', + amount: '70000.00', + status: 'completed', + phonePeOrderId: 'order_ballroom_001', + phonePeTransactionId: 'pay_ballroom_001', + phonePeTransactionRef: 'sig_ballroom_001_abc123xyz', + paidAt: new Date('2026-06-14T10:05:00Z'), + }, + // 2. Payment for Booking 2 (Pending) + { + id: '550e8400-e29b-41d4-a716-446655440071', + bookingId: '550e8400-e29b-41d4-a716-446655440051', + amount: '3600.00', + status: 'pending', + phonePeOrderId: 'order_tech_001', + phonePeTransactionId: null, + phonePeTransactionRef: null, + paidAt: null, + }, + // 3. Payment for Booking 3 (Failed) + { + id: '550e8400-e29b-41d4-a716-446655440072', + bookingId: '550e8400-e29b-41d4-a716-446655440052', + amount: '40000.00', + status: 'failed', + phonePeOrderId: 'order_bistro_001', + phonePeTransactionId: 'pay_bistro_001_failed', + phonePeTransactionRef: null, + paidAt: null, + }, + // 4. Payment for Booking 4 (Failed) + { + id: '550e8400-e29b-41d4-a716-446655440073', + bookingId: '550e8400-e29b-41d4-a716-446655440053', + amount: '500.00', + status: 'failed', + phonePeOrderId: 'order_cozy_001', + phonePeTransactionId: null, + phonePeTransactionRef: null, + paidAt: null, + }, + // 5. Payment for Booking 5 (Completed) + { + id: '550e8400-e29b-41d4-a716-446655440074', + bookingId: '550e8400-e29b-41d4-a716-446655440054', + amount: '35000.00', + status: 'completed', + phonePeOrderId: 'order_ballroom_002', + phonePeTransactionId: 'pay_ballroom_002', + phonePeTransactionRef: 'sig_ballroom_002_def456uvw', + paidAt: new Date('2026-06-14T15:20:00Z'), + }, + // 6. Payment for Booking 6 (Completed) + { + id: '550e8400-e29b-41d4-a716-446655440075', + bookingId: '550e8400-e29b-41d4-a716-446655440055', + amount: '1200.00', + status: 'completed', + phonePeOrderId: 'order_cozy_002', + phonePeTransactionId: 'pay_cozy_002', + phonePeTransactionRef: 'sig_cozy_002_ghi789rst', + paidAt: new Date('2026-06-13T09:12:00Z'), + }, + // 7. Payment for Booking 7 (Completed) + { + id: '550e8400-e29b-41d4-a716-446655440076', + bookingId: '550e8400-e29b-41d4-a716-446655440056', + amount: '1800.00', + status: 'completed', + phonePeOrderId: 'order_tech_002', + phonePeTransactionId: 'pay_tech_002', + phonePeTransactionRef: 'sig_tech_002_jkl012opq', + paidAt: new Date('2026-06-15T08:30:00Z'), + }, + ]; + + await db.insert(paymentsTable).values(paymentsData).onConflictDoNothing(); + console.log('✓ Payments done'); +}; diff --git a/backend/src/services/analyticsServices.js b/backend/src/services/analyticsServices.js new file mode 100644 index 0000000000..4fcd32c000 --- /dev/null +++ b/backend/src/services/analyticsServices.js @@ -0,0 +1,92 @@ +import { db } from '../db/index.js'; +import { venuesTable } from '../models/venueModel.js'; +import { usersTable } from '../models/userModel.js'; +import { bookingsTable } from '../models/bookingModel.js'; +import { paymentsTable } from '../models/paymentModel.js'; +import { eq, sql, count } from 'drizzle-orm'; + +const analyticServices = { + adminDashboardStats: async () => { + const [venueCount] = await db + .select({ count: count() }) + .from(venuesTable); + + const [userCount] = await db + .select({ count: count() }) + .from(usersTable); + + const [bookingCount] = await db + .select({ count: count() }) + .from(bookingsTable); + + const [pendingVenueCount] = await db + .select({ count: count() }) + .from(venuesTable) + .where(eq(venuesTable.approvalStatus, 'pending')); + + const [approvedVenueCount] = await db + .select({ count: count() }) + .from(venuesTable) + .where(eq(venuesTable.approvalStatus, 'approved')); + + const [rejectedVenueCount] = await db + .select({ count: count() }) + .from(venuesTable) + .where(eq(venuesTable.approvalStatus, 'rejected')); + + const [activeVenueCount] = await db + .select({ count: count() }) + .from(venuesTable) + .where(eq(venuesTable.isActive, true)); + + const [approvedBookingCount] = await db + .select({ count: count() }) + .from(bookingsTable) + .where(eq(bookingsTable.status, 'approved')); + + const [pendingBookingCount] = await db + .select({ count: count() }) + .from(bookingsTable) + .where(eq(bookingsTable.status, 'pending')); + + const [cancelledBookingCount] = await db + .select({ count: count() }) + .from(bookingsTable) + .where(eq(bookingsTable.status, 'cancelled')); + + const [rejectedBookingCount] = await db + .select({ count: count() }) + .from(bookingsTable) + .where(eq(bookingsTable.status, 'rejected')); + + const [completedBookingCount] = await db + .select({ count: count() }) + .from(bookingsTable) + .where(eq(bookingsTable.status, 'completed')); + + const [revenueResult] = await db + .select({ + totalRevenue: sql`COALESCE(SUM(${paymentsTable.amount}), 0)`, + }) + .from(paymentsTable) + .where(eq(paymentsTable.status, 'completed')); + + return { + totalVenues: Number(venueCount.count), + totalUsers: Number(userCount.count), + totalBookings: Number(bookingCount.count), + totalPendingApprovals: Number(pendingVenueCount.count), + totalApprovedVenues: Number(approvedVenueCount.count), + totalRejectedVenues: Number(rejectedVenueCount.count), + totalActiveVenues: Number(activeVenueCount.count), + totalApprovedBookings: Number(approvedBookingCount.count), + totalPendingBookings: Number(pendingBookingCount.count), + totalCancelledBookings: Number(cancelledBookingCount.count), + totalRejectedBookings: Number(rejectedBookingCount.count), + totalCompletedBookings: Number(completedBookingCount.count), + totalRevenue: Number(revenueResult.totalRevenue), + }; + }, +}; + +export default analyticServices; diff --git a/backend/src/services/authServices.js b/backend/src/services/authServices.js new file mode 100644 index 0000000000..052ebabaf7 --- /dev/null +++ b/backend/src/services/authServices.js @@ -0,0 +1,109 @@ +import jwt from 'jsonwebtoken'; +import db from '../db/index.js'; +import { usersTable } from '../models/index.js'; +import { AppError } from '../handlers/error_handlers.js'; +import { hashPassword } from '../utils/utils.js'; +import { eq } from 'drizzle-orm'; + +export default { + getUserByEmail: async function (email) { + const [existingUser] = await db + .select({ + id: usersTable.id, + email: usersTable.email, + username: usersTable.username, + salt: usersTable.salt, + password: usersTable.password, + role: usersTable.role, + }) + .from(usersTable) + .where(eq(usersTable.email, email)); + return existingUser; + }, + + register: async function (payload) { + const { email, role, password, username } = payload; + + const currentUser = await this.getUserByEmail(email); + if (currentUser) { + throw new AppError({ + message: 'User already exists with this email', + statusCode: 400, + errorCode: 'USER_EXISTS', + }); + } + + const { password: hashedPassword, salt } = await hashPassword(password); + + const [newUser] = await db + .insert(usersTable) + .values({ + email, + username, + password: hashedPassword, + salt, + role, + }) + .returning({ + id: usersTable.id, + email: usersTable.email, + username: usersTable.username, + role: usersTable.role, + }); + return { data: newUser }; + }, + + login: async function (payload) { + const { email, password } = payload; + const user = await this.getUserByEmail(email); + if (!user) { + throw new AppError({ + message: 'No user found with this email', + statusCode: 404, + errorCode: 'USER_NOT_FOUND', + }); + } + + const { password: hashedPassword, salt } = await hashPassword(password, user.salt); + + if (hashedPassword !== user.password) { + throw new AppError({ + message: 'Incorrect password', + statusCode: 401, + errorCode: 'INVALID_CREDENTIALS', + }); + } + + const response = await jwt.sign( + { userId: user.id, email: user.email, role: user.role }, + process.env.JWT_SECRET, + { expiresIn: '1h' } + ); + + console.log('Generated JWT:', response); + + return { + token: response, + data: { + id: user.id, + email: user.email, + username: user.username, + role: user.role, + }, + }; + }, + + getById: async function (id) { + const [user] = await db + .select({ + id: usersTable.id, + email: usersTable.email, + username: usersTable.username, + role: usersTable.role, + }) + .from(usersTable) + .where(eq(usersTable.id, id)); + return user; + } +}; + diff --git a/backend/src/services/bookingServices.js b/backend/src/services/bookingServices.js new file mode 100644 index 0000000000..9778be7157 --- /dev/null +++ b/backend/src/services/bookingServices.js @@ -0,0 +1,265 @@ +import { db } from '../db/index.js'; +import { bookingsTable } from '../models/bookingModel.js'; +import { venuesTable } from '../models/venueModel.js'; +import { paymentsTable } from '../models/paymentModel.js'; +import { and, eq, ne, lte, gte, inArray } from 'drizzle-orm'; +import pricingServices from './pricingServices.js'; +import { AppError } from '../handlers/error_handlers.js'; +import { StandardCheckoutPayRequest } from '@phonepe-pg/pg-sdk-node'; +import { phonePeClient } from '../utils/phonepe.js'; +import notificationService from './notificationService.js'; + +export default { + checkAvailability: async function (venueId, monthParam) { + const [year, month] = monthParam.split('-').map(Number); + const monthStartStr = `${year}-${String(month).padStart(2, '0')}-01`; + // "2026-06-01" + + const lastDay = new Date(year, month, 0).getDate(); // gets the day number e.g. 30 + const monthEndStr = `${year}-${String(month).padStart(2, '0')}-${lastDay}`; + // "2026-06-30" + + const bookings = await db.query.bookingsTable.findMany({ + where: and( + eq(bookingsTable.venueId, venueId), + ne(bookingsTable.status, 'cancelled'), + lte(bookingsTable.startDate, monthEndStr), + gte(bookingsTable.endDate, monthStartStr) + ), + columns: { + startDate: true, + endDate: true, + startTime: true, // added + endTime: true, // added + status: true, + }, +}); +return bookings; + }, + + bookVenue: async function (bookerId, venueId, startDate, endDate, startTime, endTime, note) { + const venueDetails = await pricingServices.priceCalc( + venueId, + startDate, + endDate, + startTime, + endTime + ); + + const booking = await db.transaction(async (tx) => { + if (venueDetails.venue.bookingType === 'daily') { + const existingBooking = await tx.query.bookingsTable.findMany({ + where: and( + eq(bookingsTable.venueId, venueId), + ne(bookingsTable.status, 'cancelled'), + lte(bookingsTable.startDate, endDate), + gte(bookingsTable.endDate, startDate) + ), + }); + + if (existingBooking.length > 0) { + throw new AppError({ + message: 'Venue is Booked for this period', + statusCode: 404, + errorCode: 'VENUE_BOOKED', + }); + } + } + + if (venueDetails.venue.bookingType === 'hourly') { + const existingBooking = await tx.query.bookingsTable.findMany({ + where: and( + eq(bookingsTable.venueId, venueId), + ne(bookingsTable.status, 'cancelled'), + eq(bookingsTable.startDate, startDate), + lte(bookingsTable.startTime, endTime), + gte(bookingsTable.endTime, startTime) + ), + }); + + if (existingBooking.length > 0) { + throw new AppError({ + message: 'Venue is Booked for this period', + statusCode: 404, + errorCode: 'VENUE_BOOKED', + }); + } + } + + const [newBooking] = await tx + .insert(bookingsTable) + .values({ + venueId, + bookerId, + startDate, + endDate, + status: 'pending', + totalAmount: venueDetails.totalAmount, + pricingSnapshot: { + breakdown: venueDetails.breakdown, + totalAmount: venueDetails.totalAmount, + }, + note, + startTime, + endTime, + }) + .returning(); + + const merchantOrderId = `booking_${newBooking.id}`; + + const orderRequest = StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(Math.round(venueDetails.totalAmount * 100)) // paisa + .redirectUrl(`${process.env.FRONTEND_URL}/payments/verify?bookingId=${newBooking.id}`) + .build(); + + const phonePeResponse = await phonePeClient.pay(orderRequest); + + await tx.insert(paymentsTable).values({ + bookingId: newBooking.id, + amount: venueDetails.totalAmount, + status: 'pending', + phonePeOrderId: merchantOrderId, + phonePeTransactionId: phonePeResponse.orderId, + }); + + return { + bookingId: newBooking.id, + redirectUrl: phonePeResponse.redirectUrl, // frontend redirects user here + totalAmount: venueDetails.totalAmount, + breakdown: venueDetails.breakdown, + }; + }); + + return booking; + }, + + verifyPayment: async function (bookingId) { + // 1. fetch payment row + const payment = await db.query.paymentsTable.findFirst({ + where: eq(paymentsTable.bookingId, bookingId), + }); + + if (!payment) + throw new AppError({ + message: 'Payment not found', + statusCode: 404, + errorCode: 'PAYMENT_NOT_FOUND', + }); + + // 2. check with PhonePe + const statusResponse = await phonePeClient.getOrderStatus(payment.phonePeOrderId); + + // 3. handle result + if (statusResponse.state === 'COMPLETED') { + await db.transaction(async (tx) => { + await tx + .update(paymentsTable) + .set({ + status: 'completed', + phonePeTransactionRef: statusResponse.paymentDetails?.[0]?.transactionId, + paidAt: new Date(), + }) + .where(eq(paymentsTable.bookingId, bookingId)); + + await tx + .update(bookingsTable) + .set({ status: 'confirmed' }) + .where(eq(bookingsTable.id, bookingId)); + }); + + const booking = await db.query.bookingsTable.findFirst({ + where: eq(bookingsTable.id, bookingId), + with: { + venue: true, // gives you venue.ownerId directly + }, + }); + + await notificationService.createNotification({ + recipientId: booking.venue.ownerId, + type: 'BOOKING_CONFIRMED', + payload: { + bookingId: booking.id, + venueName: booking.venue.name, + startDate: booking.startDate, + endDate: booking.endDate, + totalAmount: booking.totalAmount, + }, + }); + + return { status: 'confirmed' }; + } + + if (statusResponse.state === 'FAILED') { + await db.transaction(async (tx) => { + await tx + .update(paymentsTable) + .set({ status: 'failed' }) + .where(eq(paymentsTable.bookingId, bookingId)); + + await tx + .update(bookingsTable) + .set({ status: 'cancelled' }) // ← frees up the dates + .where(eq(bookingsTable.id, bookingId)); + }); + + return { status: 'failed' }; + } + + return { status: 'pending' }; + }, + + getUserBookings: async function (userId) { + const result = await db.query.bookingsTable.findMany({ + where: eq(bookingsTable.bookerId, userId), + with: { + venue: { + columns: { + id: true, + name: true, + city: true, + images: true, + bookingType: true, + }, + }, + }, + orderBy: (bookingsTable, { desc }) => [desc(bookingsTable.createdAt)], + }); + return result; + }, + + getOwnerBookings: async function (ownerId) { + const venues = await db.query.venuesTable.findMany({ + where: eq(venuesTable.ownerId, ownerId), + columns: { id: true }, + }); + + const venueIds = venues.map((v) => v.id); + if (venueIds.length === 0) return []; + + const bookings = await db.query.bookingsTable.findMany({ + where: inArray(bookingsTable.venueId, venueIds), + with: { + venue: { + columns: { + id: true, + name: true, + city: true, + images: true, + bookingType: true, + }, + }, + booker: { + columns: { + id: true, + username: true, + email: true, + }, + }, + }, + orderBy: (bookingsTable, { desc }) => [desc(bookingsTable.createdAt)], + }); + + return bookings; + }, +}; diff --git a/backend/src/services/conversationService.js b/backend/src/services/conversationService.js new file mode 100644 index 0000000000..31ff077eab --- /dev/null +++ b/backend/src/services/conversationService.js @@ -0,0 +1,134 @@ +import db from '../db/index.js'; +import { conversationsTable, messagesTable } from '../models/index.js'; +import { usersTable } from '../models/userModel.js'; +import { eq, and, desc, lt } from 'drizzle-orm'; +import { getClients } from '../utils/wsClient.js'; + +export default { + findOrCreate: async function (userId, ownerId) { + const [existing] = await db + .select() + .from(conversationsTable) + .where( + and( + eq(conversationsTable.userId, userId), + eq(conversationsTable.ownerId, ownerId) + ) + ); + + if (existing) return existing; + + const [newConversation] = await db + .insert(conversationsTable) + .values({ userId, ownerId }) + .returning(); + + return newConversation; + }, + + getMessages: async function (conversationId, cursor, limit = 20) { + const query = db + .select() + .from(messagesTable) + .where( + cursor + ? and( + eq(messagesTable.conversationId, conversationId), + lt(messagesTable.createdAt, new Date(cursor)) + ) + : eq(messagesTable.conversationId, conversationId) + ) + .orderBy(desc(messagesTable.createdAt)) + .limit(limit); + + const messages = await query; + return messages.reverse(); // oldest first for UI rendering + }, + + sendMessage: async function (conversationId, senderId, content, venueId = null) { + // 1. persist to DB first — source of truth + const [message] = await db + .insert(messagesTable) + .values({ conversationId, senderId, content, venueId }) + .returning(); + + // 2. update lastMessageAt on conversation + await db + .update(conversationsTable) + .set({ lastMessageAt: new Date() }) + .where(eq(conversationsTable.id, conversationId)); + + // 3. get the conversation to find the recipient + const [conversation] = await db + .select() + .from(conversationsTable) + .where(eq(conversationsTable.id, conversationId)); + + // 4. recipient is whoever isn't the sender + const recipientId = conversation.userId === senderId + ? conversation.ownerId + : conversation.userId; + + // 5. push to recipient and sender via WS if online + const wsPayload = JSON.stringify({ + type: 'NEW_MESSAGE', + payload: message, + }); + + const pushToUser = (userId) => { + const sockets = getClients(userId); + sockets.forEach((ws) => { + if (ws.readyState === 1) { + ws.send(wsPayload); + } + }); + }; + + pushToUser(recipientId); + pushToUser(senderId); + + return message; + }, + + getConversations: async function (userId, role) { + const conversations = await db + .select() + .from(conversationsTable) + .where( + role === 'owner' + ? eq(conversationsTable.ownerId, userId) + : eq(conversationsTable.userId, userId) + ) + .orderBy(desc(conversationsTable.lastMessageAt)); + + if (!conversations.length) return []; + + return Promise.all( + conversations.map(async (conversation) => { + const otherUserId = + role === 'owner' ? conversation.userId : conversation.ownerId; + + const [otherParticipant] = await db + .select({ id: usersTable.id, username: usersTable.username }) + .from(usersTable) + .where(eq(usersTable.id, otherUserId)); + + const [lastMessage] = await db + .select({ + content: messagesTable.content, + createdAt: messagesTable.createdAt, + }) + .from(messagesTable) + .where(eq(messagesTable.conversationId, conversation.id)) + .orderBy(desc(messagesTable.createdAt)) + .limit(1); + + return { + ...conversation, + otherParticipant: otherParticipant || null, + lastMessage: lastMessage || null, + }; + }) + ); + }, +}; \ No newline at end of file diff --git a/backend/src/services/favouriteServices.js b/backend/src/services/favouriteServices.js new file mode 100644 index 0000000000..18a1d5dfa0 --- /dev/null +++ b/backend/src/services/favouriteServices.js @@ -0,0 +1,29 @@ +import db from "../db/index.js" +import {userFavourites} from "../models/userModel.js" +import {eq,and} from 'drizzle-orm' + +export default { + addFavorite : async function(userId,venueId){ + const result = await db.insert(userFavourites).values({userId,venueId}).returning(); + return result; + }, + + deleteFavorite: async function(venueId,userId){ + await db.delete(userFavourites).where( + and( + eq(userFavourites.userId,userId), + eq(userFavourites.venueId,venueId) + ) + ) + }, + + getFavorites: async function(userId){ + const result = await db.query.userFavourites.findMany({ + where: eq(userFavourites.userId, userId), + with: { + venue: true + } + }) + return result; + } +} \ No newline at end of file diff --git a/backend/src/services/notificationService.js b/backend/src/services/notificationService.js new file mode 100644 index 0000000000..86af744f17 --- /dev/null +++ b/backend/src/services/notificationService.js @@ -0,0 +1,47 @@ +import db from '../db/index.js'; +import { notificationsTable } from '../models/notificationModel.js'; +import { getClients } from '../utils/sseClient.js'; +import {and,eq} from 'drizzle-orm'; + +export default { + createNotification: async function ({ recipientId, type, payload }) { + const [notification] = await db + .insert(notificationsTable) + .values({ + recipientId, + type, + payload, + }) + .returning(); + + const connections = getClients(recipientId); + if(connections) { + // Push to every open tab simultaneously. + // SSE wire format is strict — must be "data: \n\n" + // The double newline signals end of message to the browser's EventSource. + // We send the full notification object (id, type, payload, createdAt) + // so the frontend can append it to the list without a separate fetch. + connections.forEach(r => r.write(`data: ${JSON.stringify(notification)}\n\n`)) + } + return notification; + }, + + getNotifications: async function (userId) { + const notifications = await db.query.notificationsTable.findMany({ + where: and(eq(notificationsTable.recipientId, userId), eq(notificationsTable.isRead, false)), + }); + return notifications; + }, + + markAllAsRead: async function (userId) { + await db + .update(notificationsTable) + .set({ isRead: true }) + .where( + and( + eq(notificationsTable.recipientId, userId), + eq(notificationsTable.isRead, false) + ) + ); + }, +}; diff --git a/backend/src/services/pricingServices.js b/backend/src/services/pricingServices.js new file mode 100644 index 0000000000..237f164bee --- /dev/null +++ b/backend/src/services/pricingServices.js @@ -0,0 +1,50 @@ +import { db } from '../db/index.js'; +import { venuesTable } from '../models/venueModel.js'; +import { findMatchingRow, classifyDay } from '../utils/utils.js'; +import { eq } from 'drizzle-orm'; + +export default { + priceCalc: async function (venueId, startDate, endDate, startTime, endTime) { + const venue = await db.query.venuesTable.findFirst({ + where: eq(venuesTable.id, venueId), + with: { + pricing: true, + }, + }); + + if (!venue) throw new Error('Venue not found') + + const bookingType = venue.bookingType; + + if ((bookingType === 'daily')) { + const dates = []; + const current = new Date(startDate); + const end = new Date(endDate); + + while (current <= end) { + dates.push(current.toISOString().split('T')[0]); + current.setDate(current.getDate() + 1); + } + + const breakdown = dates.map((date) => { + const dayType = classifyDay(date); + console.log(venue,"gggggggggggggggggggggggggggggg") + const pricingRow = findMatchingRow(venue.pricing, dayType); + console.log(dayType,venue,pricingRow,"pricingRowpricingRow") + return { date, dayType, amount: parseFloat(pricingRow.price) }; + }); + const totalAmount = breakdown.reduce((sum, d) => sum + d.amount, 0); + return {venue, breakdown, totalAmount }; + } + + if ((bookingType === 'hourly')) { + const [startH, startM] = startTime.split(':').map(Number); // [9, 0] + const [endH, endM] = endTime.split(':').map(Number); // [14, 0] + const dayType = classifyDay(startDate); + const hours = endH + endM / 60 - (startH + startM / 60); + const pricingRow = findMatchingRow(venue.pricing, dayType); + const amount = hours * parseFloat (pricingRow.price); + return {venue, breakdown: [{ date: startDate, hours, dayType, amount }], totalAmount: amount }; + } + }, +}; diff --git a/backend/src/services/venueServices.js b/backend/src/services/venueServices.js new file mode 100644 index 0000000000..951a0922ab --- /dev/null +++ b/backend/src/services/venueServices.js @@ -0,0 +1,307 @@ +import { db } from '../db/index.js'; +import { venuesTable, venuePricing } from '../models/venueModel.js'; +import { venueAmenities } from '../models/amenityModel.js'; +import { amenities } from '../models/amenityModel.js'; +import { eq, gte, ilike, and, or, sql } from 'drizzle-orm'; +import { AppError } from '../handlers/error_handlers.js'; + +const venueServices = { + isReadyForReview: (payload) => { + const check = { + hasPincode: !!payload.pincode, + hasImages: payload.images?.length > 0, + hasOpenDays: payload.openDays?.length > 0, + hasOpenTime: !!payload.openTime, + hasCloseTime: !!payload.closeTime, + hasPricing: payload.pricing?.length > 0, + hasAmenities: payload.venueAmenities?.length > 0, + }; + // console.log("check",check) + const isReady = Object.values(check).every(Boolean); + return { isReady, check }; + }, + + addVenue: async function (payload) { + const status = this.isReadyForReview(payload); + + // console.log(payload,status,"payload") + + const venue = await db.transaction(async (tx) => { + const venueData = { + ownerId: payload.ownerId, + name: payload.name, + description: payload.description, + type: payload.type, + address: payload.address, + city: payload.city, + state: payload.state, + pincode: payload.pincode, + latitude: payload.latitude, + longitude: payload.longitude, + capacity: payload.capacity, + images: payload.images, + openDays: payload.openDays, + openTime: payload.openTime, + closeTime: payload.closeTime, + minBookingHours: payload.minBookingHours, + bookingType: payload.bookingType || 'daily', + isActive: false, + approvalStatus: status.isReady ? 'pending' : 'draft', + }; + + console.log(venueData, 'venue'); + const [venue] = await tx.insert(venuesTable).values(venueData).returning(); + + if (payload.pricing && payload.pricing.length > 0) { + await tx.insert(venuePricing).values( + payload.pricing.map((p) => ({ + venueId: venue.id, + dayType: p.dayType, + price: p.price, + minHours: p.minHours, + validFrom: new Date(), + validTo: p.validTo ? new Date(p.validTo) : null, + })) + ); + } + + if (payload.venueAmenities && payload.venueAmenities.length > 0) { + await tx.insert(venueAmenities).values( + payload.venueAmenities.map((amenityId) => ({ + venueId: venue.id, + amenityId, + })) + ); + } + + return venue; + }); + + return { + venue, + isReadyForReview: status.isReady, + reviewChecklist: status.check, + }; + }, + +updateVenue: async function(payload, id) { + const { venueAmenities:venueAmenitiesList, ...venueData } = payload + + const result = await db.transaction(async (tx) => { + const [response] = await tx + .update(venuesTable) + .set({ ...venueData, updatedAt: new Date() }) + .where(eq(venuesTable.id, id)) + .returning() + + if (venueAmenitiesList && venueAmenitiesList.length > 0) { + await tx.delete(venueAmenities) + .where(eq(venueAmenities.venueId, id)) + + await tx.insert(venueAmenities) + .values(venueAmenitiesList.map(amenityId => ({ + venueId: id, + amenityId + }))) + } + + return response + }) + + return result +}, + + getOwnerVenues: async function (ownerId) { + const response = await db.query.venuesTable.findMany({ + where: eq(venuesTable.ownerId, ownerId), + with: { + pricing: true, + venueAmenities: { + with: { + amenity: true, + }, + }, + }, + }); + + return response; + }, + + getVenues: async function (payload, { isAdmin = false } = {}) { + const { page = 1, pageSize = 10, includeInactive, ...filters } = payload || {}; + const limit = parseInt(pageSize, 10) || 10; + const currentPage = parseInt(page, 10) || 1; + const offset = (currentPage - 1) * limit; + + const conditions = []; + + if (isAdmin) { + // Admin uses the same /venues route but sees every venue. + } else { + conditions.push(eq(venuesTable.approvalStatus, 'approved')); + if (includeInactive !== 'true' && includeInactive !== true) { + conditions.push(eq(venuesTable.isActive, true)); + } + } + + const filterHandlers = { + city: (val) => ilike(venuesTable.city, `%${val}%`), + type: (val) => eq(venuesTable.type, val), + capacity: (val) => gte(venuesTable.capacity, parseInt(val, 10)), + search: (val) => or(ilike(venuesTable.name, `%${val}%`), ilike(venuesTable.city, `%${val}%`)), + approvalStatus: (val) => eq(venuesTable.approvalStatus, val), + }; + + Object.entries(filters).forEach(([key, val]) => { + if (filterHandlers[key] && val !== undefined && val !== '') { + conditions.push(filterHandlers[key](val)); + } + }); + + const whereClause = conditions.length > 0 ? and(...conditions) : undefined; + + const countQuery = db.select({ count: sql`count(*)` }).from(venuesTable); + const [{ count }] = whereClause + ? await countQuery.where(whereClause) + : await countQuery; + + const withRelations = { + pricing: true, + venueAmenities: { + with: { amenity: true }, + }, + }; + + if (isAdmin) { + withRelations.owner = { + columns: { + id: true, + username: true, + email: true, + }, + }; + } + + const rows = await db.query.venuesTable.findMany({ + where: whereClause, + with: withRelations, + ...(isAdmin && { + orderBy: (table, { desc: descOrder }) => [descOrder(table.updatedAt)], + }), + limit, + offset, + }); + + return { + rows, + total: parseInt(count, 10) || 0, + page: currentPage, + pageSize: limit, + }; + }, + + getVenueDetails: async function (venueId) { + const result = await db.query.venuesTable.findFirst({ + where: eq(venuesTable.id, venueId), + with: { + pricing: true, + venueAmenities: { + with: { amenity: true }, + }, + }, + }); + console.log(result,"resultresultresultresultresultresultresult") + return result; + }, + + getPendingVenues: async function () { + const pendingVenues = await db.query.venuesTable.findMany({ + where: eq(venuesTable.approvalStatus, 'pending'), + with: { + owner: { + columns: { + id: true, + username: true, + email: true, + }, + }, + }, + orderBy: (venuesTable, { asc }) => [asc(venuesTable.createdAt)], + }); + return pendingVenues; + }, + + approveVenue: async function (venueId) { + const [response] = await db + .update(venuesTable) + .set({ approvalStatus: 'approved', updatedAt: new Date() }) + .where(eq(venuesTable.id, venueId)) + .returning(); + + return response; + }, + + rejectVenue: async function (venueId, reason) { + const [response] = await db + .update(venuesTable) + .set({ approvalStatus: 'rejected', updatedAt: new Date(), adminNote: reason }) + .where(eq(venuesTable.id, venueId)) + .returning(); + + return response; + }, + + deactivateVenue: async function (venueId, reason) { + const [response] = await db + .update(venuesTable) + .set({ isActive: false, updatedAt: new Date() }) + .where(eq(venuesTable.id, venueId)) + .returning(); + + return response; + }, + + activateVenue: async function (venueId, reason) { + const [response] = await db + .update(venuesTable) + .set({ isActive: true, updatedAt: new Date() }) + .where(eq(venuesTable.id, venueId)) + .returning(); + + return response; + }, + + checkSubmission: async function (venueId) { + const venue = await db.query.venuesTable.findFirst({ + where: eq(venuesTable.id, venueId), + with: { + pricing: true, + venueAmenities: true, + }, + }); + + const checkStatus = this.isReadyForReview(venue); + if (checkStatus.isReady) { + const response = await db + .update(venuesTable) + .set({ approvalStatus: 'pending' }) + .where(eq(venuesTable.id, venueId)); + return response; + } + if (!checkStatus.isReady) { + throw new AppError({ + message: 'Venue profile is incomplete', + statusCode: 400, + errorCode: 'VENUE_NOT_READY', + data: checkStatus.check, // so frontend knows exactly what's missing + }); + } + }, + + getAmenities: async function(){ + const result = await db.select().from(amenities); + return result + } +}; + +export default venueServices; diff --git a/backend/src/utils/phonePe.js b/backend/src/utils/phonePe.js new file mode 100644 index 0000000000..d7e874e160 --- /dev/null +++ b/backend/src/utils/phonePe.js @@ -0,0 +1,8 @@ +import { StandardCheckoutClient, Env } from 'pg-sdk-node'; + +const clientId = "M22G86ILJZX5A_2606151747"; +const clientSecret = "NzVkMWIxZmEtZGMzNS00ZjM1LWFlNmUtYzI4NjQ1ZjkyYzE4"; +const clientVersion = 1; //insert your client version here +const env = Env.SANDBOX; //change to Env.PRODUCTION when you go live + +export const phonePeClient = StandardCheckoutClient.getInstance(clientId, clientSecret, clientVersion, env); \ No newline at end of file diff --git a/backend/src/utils/sseClient.js b/backend/src/utils/sseClient.js new file mode 100644 index 0000000000..df3c72e3ff --- /dev/null +++ b/backend/src/utils/sseClient.js @@ -0,0 +1,26 @@ + + +const clients = new Map() + +export const addClient = (userId, res) => { + // get existing array or empty, push new res, set back + const existing = clients.get(userId) ?? [] + existing.push(res) + return clients.set(userId,existing) +} + + +export const removeClient = (userId, res) => { + // filter out this specific res + // if array is empty, delete the key entirely + const updated = clients.get(userId)?.filter(r => r !== res) ?? [] + if(updated.length === 0){ + clients.delete(userId) + } + return clients.set(userId,updated); +}; + +export const getClients = (userId) => { + // return the array, or empty array if not found + return clients.get(userId); +} \ No newline at end of file diff --git a/backend/src/utils/utils.js b/backend/src/utils/utils.js new file mode 100644 index 0000000000..8c30585b2e --- /dev/null +++ b/backend/src/utils/utils.js @@ -0,0 +1,34 @@ +import { randomBytes, createHmac } from 'crypto'; +import jwt from 'jsonwebtoken'; + +export const verifyToken = (token) => { + return jwt.verify(token, process.env.JWT_SECRET); +}; + +export const hashPassword = async (password, UserSalt) => { + const salt = UserSalt || randomBytes(256).toString('hex'); + const hashedPassword = createHmac('sha256', salt).update(password).digest('hex'); + return { password: hashedPassword, salt }; +}; + +export function classifyDay(dateString) { + const day = new Date(dateString).getDay() + // getDay() returns 0=Sunday, 1=Monday ... 6=Saturday + if (day === 0 || day === 6) return 'weekend' + return 'weekday' +} + +export function findMatchingRow(pricing,dateType){ + return pricing.find((e)=> e.dayType === dateType); + +} + +export const parseCookies = (req) => { + const cookieHeader = req.headers.cookie || ''; + return Object.fromEntries( + cookieHeader.split(';').map(c => { + const [key, ...val] = c.trim().split('='); + return [key, val.join('=')]; + }) + ); +}; \ No newline at end of file diff --git a/backend/src/utils/wsClient.js b/backend/src/utils/wsClient.js new file mode 100644 index 0000000000..338ddd650c --- /dev/null +++ b/backend/src/utils/wsClient.js @@ -0,0 +1,23 @@ + + +const wsClients = new Map(); + +export const addClient = (userId, ws) => { + const existing = wsClients.get(userId) || []; + wsClients.set(userId, [...existing, ws]); +}; + + +export const removeClient = (userId, ws) => { + const existing = wsClients.get(userId) || []; + const updated = existing.filter(client => client !== ws); + if (updated.length === 0) { + wsClients.delete(userId); + } else { + wsClients.set(userId, updated); + } +}; + +export const getClients = (userId) => { + return wsClients.get(userId) || []; +}; \ No newline at end of file diff --git a/backend/src/validations/authValidations.js b/backend/src/validations/authValidations.js new file mode 100644 index 0000000000..373ac1562a --- /dev/null +++ b/backend/src/validations/authValidations.js @@ -0,0 +1,8 @@ +import { email, z } from 'zod'; + +export const registerSchema = z.object({ + username: z.string().min(3, 'Username must be at least 3 characters long'), + email: z.string().email('Invalid email address'), + password: z.string().min(6, 'Password must be at least 6 characters long'), + role: z.enum(['user', 'owner','admin'], "Role must be either 'user' or 'owner'"), +}); diff --git a/backend/src/validations/venueValidation.js b/backend/src/validations/venueValidation.js new file mode 100644 index 0000000000..0afc0805d2 --- /dev/null +++ b/backend/src/validations/venueValidation.js @@ -0,0 +1,46 @@ +import { z } from 'zod'; + +export const pricingSchema = z.object({ + dayType: z.enum(['weekday', 'weekend', 'holiday']), + price: z.coerce.number().positive('Price must be positive'), + minHours: z.coerce.number().int().positive().default(1), + validFrom: z.string().optional(), + validTo: z.string().optional(), +}); + +export const imageSchema = z.object({ + url: z.string().url('Each image must be a valid URL'), + isPrimary: z.boolean().default(false), + order: z.coerce.number().int().default(0), +}); + +export const venueSchema = z.object({ + name: z.string().min(3, 'Venue name must be at least 3 characters long'), + description: z.string().min(10, 'Description must be at least 10 characters long'), + type: z.enum([ + 'cafe', + 'auditorium', + 'studio', + 'outdoor', + 'banquet', + 'coworking', + 'art_space', + 'rooftop', + 'other', + ]), + address: z.string().min(5, 'Address must be at least 5 characters long'), + city: z.string().min(2, 'City must be at least 2 characters long'), + state: z.string().min(2, 'State must be at least 2 characters long'), + capacity: z.coerce.number().int().positive('Capacity must be a positive integer'), + pincode: z.string().min(4).max(10).optional(), + latitude: z.coerce.number().optional(), + longitude: z.coerce.number().optional(), + images: z.array(imageSchema).optional().default([]), + openDays: z.array(z.string()).optional().default([]), + openTime: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/, 'Invalid time format, use HH:MM').optional(), + closeTime: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/, 'Invalid time format, use HH:MM').optional(), + minBookingHours: z.coerce.number().int().positive().default(1), + bookingType: z.enum(['hourly', 'daily']).default('daily'), + venueAmenities: z.array(z.string().uuid('Each amenity must be a valid UUID')).optional().default([]), + pricing: z.array(pricingSchema).optional().default([]), +}); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..48392d5287 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +services: + + backend: + build: ./backend + ports: + - "5005:5005" + env_file: + - ./backend/.env + depends_on: + - db + + db: + image: postgres:16 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: bookmyvenue + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + frontend: + build: ./frontend + ports: + - "5173:5173" + depends_on: + - backend + +volumes: + postgres_data: \ No newline at end of file diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000000..60d94f71cd --- /dev/null +++ b/frontend/.env @@ -0,0 +1,6 @@ +VITE_CLOUDINARY_CLOUD_NAME=djoa1bbwe +VITE_CLOUDINARY_UPLOAD_PRESET=bookMyVenue +VITE_API_URL= http://localhost:5005 + +# Chat/messaging feature (disabled by default). Set to true to enable. +# VITE_ENABLE_CHAT=true \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000000..cf3544aa65 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,13 @@ +FROM node:20 + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +EXPOSE 5173 + +CMD ["npm", "run", "dev", "--", "--host"] \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000000..a36934d874 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000000..ea36dd3dc4 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,21 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000000..ec1998ebd9 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,17 @@ + + + + + + + BookMyVenue — Find & Book Event Venues + + + + + + +
+ + + diff --git a/frontend/js.config.json b/frontend/js.config.json new file mode 100644 index 0000000000..df83de409a --- /dev/null +++ b/frontend/js.config.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + } +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000000..bf95da1a7b --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3433 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@reduxjs/toolkit": "^2.12.0", + "@tailwindcss/vite": "^4.3.0", + "leaflet": "^1.9.4", + "react": "^19.2.6", + "react-datepicker": "^9.1.0", + "react-day-picker": "^10.0.1", + "react-dom": "^19.2.6", + "react-hot-toast": "^2.6.0", + "react-icons": "^5.6.0", + "react-leaflet": "^5.0.0", + "react-redux": "^9.3.0", + "react-router-dom": "^7.16.0", + "sass": "^1.100.0", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.9.1", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "vite": "^8.0.12" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@date-fns/tz": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.5.0.tgz", + "integrity": "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==", + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.20", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.20.tgz", + "integrity": "sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.9", + "@floating-ui/utils": "^0.2.12", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@react-leaflet/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", + "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "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==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", + "integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz", + "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.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", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/goober": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", + "integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", + "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-datepicker": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-9.1.0.tgz", + "integrity": "sha512-lOp+m5bc+ttgtB5MHEjwiVu4nlp4CvJLS/PG1OiOe5pmg9kV73pEqO8H0Geqvg2E8gjqTaL9eRhSe+ZpeKP3nA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.15", + "clsx": "^2.1.1", + "date-fns": "^4.1.0" + }, + "peerDependencies": { + "date-fns-tz": "^3.0.0", + "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "date-fns-tz": { + "optional": true + } + } + }, + "node_modules/react-day-picker": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-10.0.1.tgz", + "integrity": "sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "date-fns": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "@types/react": ">=16.8.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-leaflet": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", + "integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^3.0.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.16.0.tgz", + "integrity": "sha512-wArC8lVyJb3+jM9OpDyW6hLCizACWkvQR/sSGqSs+o5uEXEtGlqdZ4v8hENR3Jad6i+LRkK93q/+bQAcvl6V1A==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.16.0.tgz", + "integrity": "sha512-kMUAbimWB5FVbF4Bce4bJsiKJWLIUHq/mEG8+CFDnCSgltptBiG5nguducmsJeGKytlCvQud9Qhzpn49iduTlA==", + "license": "MIT", + "dependencies": { + "react-router": "7.16.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "node_modules/sass": { + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.100.0.tgz", + "integrity": "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==", + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "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, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "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/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000000..358f7bfc33 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,40 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.12.0", + "@tailwindcss/vite": "^4.3.0", + "leaflet": "^1.9.4", + "react": "^19.2.6", + "react-datepicker": "^9.1.0", + "react-day-picker": "^10.0.1", + "react-dom": "^19.2.6", + "react-hot-toast": "^2.6.0", + "react-icons": "^5.6.0", + "react-leaflet": "^5.0.0", + "react-redux": "^9.3.0", + "react-router-dom": "^7.16.0", + "sass": "^1.100.0", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.9.1", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "vite": "^8.0.12" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000000..6893eb1323 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 0000000000..e9522193d9 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000000..bd8ceb4763 --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,187 @@ + +@import 'leaflet/dist/leaflet.css'; + +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000000..2fa74673e3 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,33 @@ +import { useEffect } from 'react' +import { Outlet } from 'react-router-dom' +import { useDispatch, useSelector } from 'react-redux' +import { setCredentials, setInitialized } from './redux/slices/authSlice' +import { selectIsAuthenticated } from './redux/slices/authSlice' +import { useGetMeQuery } from '../src/features/auth/authApi.js' +import { PageLoader } from './components/ui/LoadingSkeleton' +import { useWebSocket } from './hooks/useWebSocket' + +const App = () => { + const dispatch = useDispatch() + const isAuthenticated = useSelector(selectIsAuthenticated) + useWebSocket() + const { data: user, isLoading,isError } = useGetMeQuery(undefined, { + skip: isAuthenticated + }) + console.log("APP - isAuthenticated:", isAuthenticated, "user:", user, "isLoading:", isLoading) + useEffect(() => { + if (user?.data) dispatch(setCredentials(user.data)) + }, [user]) + + useEffect(() => { + if (isError) { + dispatch(setInitialized()) + } + }, [isError]) + + if (isLoading) return + + return +} + +export default App \ No newline at end of file diff --git a/frontend/src/api/conversationApi.js b/frontend/src/api/conversationApi.js new file mode 100644 index 0000000000..44afa23b56 --- /dev/null +++ b/frontend/src/api/conversationApi.js @@ -0,0 +1,37 @@ +import { baseApi } from '../redux/api/baseApi'; + +export const conversationApi = baseApi.injectEndpoints({ + endpoints: (builder) => ({ + getConversations: builder.query({ + query: () => '/conversations', + providesTags: ['Conversation'], + transformResponse: (response) => response?.data ?? response, + }), + findOrCreateConversation: builder.mutation({ + query: (body) => ({ + url: '/conversations/find-or-create', + method: 'POST', + body, + }), + invalidatesTags: ['Conversation'], + transformResponse: (response) => response?.data ?? response, + }), + getMessages: builder.query({ + query: ({ conversationId, cursor, limit = 20 }) => ({ + url: `/conversations/${conversationId}/messages`, + params: { cursor, limit }, + }), + providesTags: (result, error, { conversationId }) => [ + { type: 'Messages', id: conversationId }, + ], + transformResponse: (response) => response?.data ?? response, + }), + }), +}); + +export const { + useGetConversationsQuery, + useFindOrCreateConversationMutation, + useGetMessagesQuery, + useLazyGetMessagesQuery, +} = conversationApi; diff --git a/frontend/src/app/store.js b/frontend/src/app/store.js new file mode 100644 index 0000000000..8b98ff8624 --- /dev/null +++ b/frontend/src/app/store.js @@ -0,0 +1,19 @@ +import { configureStore } from "@reduxjs/toolkit"; +import { baseApi } from "../redux/api/baseApi"; +import authReducer from '../redux/slices/authSlice' +import adminAuthReducer from '../redux/slices/adminAuthSlice'; +import notificationReducer from '../redux/slices/notificationSlice' +import chatReducer from '../redux/slices/chatSlice' +import '../api/conversationApi' + +export const store = configureStore({ + reducer: { + [baseApi.reducerPath]: baseApi.reducer, + auth: authReducer, + adminAuth: adminAuthReducer, + notification: notificationReducer, + chat: chatReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat(baseApi.middleware) +}) \ No newline at end of file diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png new file mode 100644 index 0000000000..02251f4b95 Binary files /dev/null and b/frontend/src/assets/hero.png differ diff --git a/frontend/src/assets/images/bookmyvenue_logo.png b/frontend/src/assets/images/bookmyvenue_logo.png new file mode 100644 index 0000000000..315d672aec Binary files /dev/null and b/frontend/src/assets/images/bookmyvenue_logo.png differ diff --git a/frontend/src/assets/images/screen.png b/frontend/src/assets/images/screen.png new file mode 100644 index 0000000000..04559069be Binary files /dev/null and b/frontend/src/assets/images/screen.png differ diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 0000000000..5101b674df --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/components/Header/Header.jsx b/frontend/src/components/Header/Header.jsx new file mode 100644 index 0000000000..b25670b718 --- /dev/null +++ b/frontend/src/components/Header/Header.jsx @@ -0,0 +1,186 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Link, NavLink } from 'react-router-dom'; +import { FiLogOut, FiCalendar, FiSearch, FiHeart, FiMenu, FiX, FiChevronDown, FiMessageSquare } from 'react-icons/fi'; +import './Header.scss'; +import { useSelector } from 'react-redux'; +import { selectCurrentUser } from '../../redux/slices/authSlice'; +import { useLogoutMutation } from '../../features/auth/authApi'; +import { adminLogout } from '../../redux/slices/adminAuthSlice'; +import { useNavigate } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; +import { isChatEnabled } from '../../config/featureFlags'; + + +function Header() { + const [mobileOpen, setMobileOpen] = useState(false); + const [dropdownOpen, setDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + + const navigate = useNavigate(); + const dispatch = useDispatch(); + const [logout , {data,error,isLoading,isSuccess,isError}] = useLogoutMutation(); + + const currentUser = useSelector(selectCurrentUser); + + const user = { + name: currentUser?.username || 'Guest User', + role: currentUser?.role || 'user', + avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=100&h=100&q=80', + }; + + useEffect(() => { + const handleClickOutside = (e) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { + setDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const navLinks = ( + <> + `nav-tab ${isActive ? 'active' : ''}`} + onClick={() => setMobileOpen(false)} + > + + Browse + + + `nav-tab ${isActive ? 'active' : ''}`} + onClick={() => setMobileOpen(false)} + > + + My Bookings + + + `nav-tab ${isActive ? 'active' : ''}`} + onClick={() => setMobileOpen(false)} + > + + Favorites + + + {isChatEnabled && ( + `nav-tab ${isActive ? 'active' : ''}`} + onClick={() => setMobileOpen(false)} + > + + Messages + + )} + + ); + +async function logoutUser() { + try { + await logout().unwrap() + dispatch(adminLogout()) + navigate('/login') + } catch (err) { + console.error('Logout failed', err) + } +} + + + return ( +
+
+
+ +
+ + + + +
+ + BookMyVenue + + + + +
+ +
+ + + +
+
+ + + {dropdownOpen && ( +
+
+ +
+ {user.name} + {user.role} +
+
+
+ setDropdownOpen(false)}> + Favorites + + setDropdownOpen(false)}> + My Bookings + +
+ +
+ )} +
+
+
+ ); +} + +export default Header; diff --git a/frontend/src/components/Header/Header.scss b/frontend/src/components/Header/Header.scss new file mode 100644 index 0000000000..c86868c717 --- /dev/null +++ b/frontend/src/components/Header/Header.scss @@ -0,0 +1,405 @@ +.main-header { + position: sticky; + z-index: 1000; + top: 0; + left: 0; + right: 0; + width: 100%; + box-sizing: border-box; + background: rgba(255, 251, 247, 0.88); + backdrop-filter: blur(20px) saturate(1.3); + -webkit-backdrop-filter: blur(20px) saturate(1.3); + border-bottom: 1px solid var(--border-subtle); + transition: all 0.3s ease; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, var(--accent-border), transparent); + opacity: 0.6; + } + + .header-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 28px; + height: 72px; + box-sizing: border-box; + max-width: 1400px; + margin: 0 auto; + width: 100%; + + @media (max-width: 768px) { + padding: 12px 16px; + height: auto; + flex-wrap: wrap; + gap: 0; + } + } + + .header-hamburger { + display: none; + margin-left: auto; + background: var(--surface-muted); + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 10px; + cursor: pointer; + color: var(--text-h); + font-size: 20px; + transition: all 0.2s ease; + + &:hover { + background: var(--accent-bg); + border-color: var(--accent-border); + color: var(--accent); + } + + @media (max-width: 768px) { + display: flex; + align-items: center; + justify-content: center; + } + } + + .header-nav-container { + display: flex; + align-items: center; + gap: 20px; + flex: 1; + justify-content: flex-end; + + @media (max-width: 768px) { + display: none; + width: 100%; + flex-direction: column; + align-items: stretch; + gap: 12px; + padding-top: 12px; + border-top: 1px solid var(--border); + margin-top: 12px; + + &.mobile-open { + display: flex; + } + } + } + + .header-row-top { + display: flex; + align-items: center; + width: auto; + gap: 12px; + } + + .header-logo-section { + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; + color: var(--text-h); + transition: transform 0.2s ease; + + &:hover { + transform: scale(1.01); + } + + .logo-icon-wrapper { + display: flex; + align-items: center; + justify-content: center; + background: var(--gradient-brand); + color: #ffffff; + padding: 8px; + border-radius: 11px; + box-shadow: 0 4px 16px var(--accent-glow); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + svg { + display: block; + } + } + + &:hover .logo-icon-wrapper { + box-shadow: 0 6px 16px rgba(170, 59, 255, 0.4); + transform: rotate(-6deg) scale(1.05); + } + + .logo-text { + font-size: 20px; + font-weight: 700; + font-family: var(--heading); + letter-spacing: -0.5px; + color: var(--text-h); + + .gradient-text { + background: var(--gradient-brand); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + } + } + + .header-nav { + display: flex; + align-items: center; + gap: 4px; + background: var(--surface-muted); + padding: 4px; + border-radius: 100px; + border: 1px solid var(--border-subtle); + + @media (max-width: 768px) { + justify-content: center; + width: 100%; + box-sizing: border-box; + } + + .nav-tab { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-radius: 100px; + color: var(--text); + font-size: 14px; + font-weight: 500; + text-decoration: none; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + border: 1px solid transparent; + + @media (max-width: 480px) { + padding: 8px 12px; + font-size: 13px; + gap: 6px; + } + + .tab-icon { + font-size: 15px; + transition: transform 0.2s ease; + } + + &:hover { + color: var(--text-h); + background: rgba(0, 0, 0, 0.02); + + @media (prefers-color-scheme: dark) { + background: rgba(255, 255, 255, 0.02); + } + + .tab-icon { + transform: translateY(-1px); + } + } + + &.active { + color: var(--accent); + background: var(--surface-elevated); + border-color: transparent; + box-shadow: var(--shadow); + + .tab-icon { + color: var(--accent); + } + } + } + } + + .headrer-nav-container, + .header-nav-container { + display: flex; + align-items: center; + justify-content: center; + } + + + + .header-user-section { + position: relative; + display: flex; + align-items: center; + + @media (max-width: 768px) { + width: 100%; + } + + .user-dropdown-trigger { + cursor: pointer; + background: none; + font-family: inherit; + } + + .dropdown-chevron { + font-size: 14px; + color: var(--text); + transition: transform 0.2s ease; + margin-left: 4px; + + &.open { transform: rotate(180deg); } + } + + .user-dropdown-menu { + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 220px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: var(--shadow-lg); + padding: 8px; + z-index: 200; + animation: fadeUp 0.2s ease both; + text-align: left; + + @media (max-width: 768px) { + position: static; + margin-top: 8px; + width: 100%; + } + } + + .dropdown-header { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + + .dropdown-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + object-fit: cover; + } + + .dropdown-name { + display: block; + font-size: 13px; + font-weight: 600; + color: var(--text-h); + } + + .dropdown-role { + display: block; + font-size: 11px; + color: var(--text); + text-transform: capitalize; + } + } + + .dropdown-divider { + height: 1px; + background: var(--border); + margin: 4px 0; + } + + .dropdown-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 10px 12px; + border: none; + background: none; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + color: var(--text-h); + text-decoration: none; + cursor: pointer; + font-family: inherit; + transition: background 0.15s ease; + + &:hover { + background: var(--surface-muted); + } + + &--danger { + color: #ef4444; + + &:hover { + background: rgba(239, 68, 68, 0.08); + } + } + } + + .user-profile-card { + display: flex; + align-items: center; + gap: 10px; + padding: 4px 10px 4px 6px; + border-radius: 30px; + border: 1px solid var(--border); + background: rgba(0, 0, 0, 0.01); + transition: all 0.2s ease; + + @media (prefers-color-scheme: dark) { + background: rgba(255, 255, 255, 0.01); + } + + &:hover { + background: rgba(0, 0, 0, 0.03); + border-color: var(--accent-border); + + @media (prefers-color-scheme: dark) { + background: rgba(255, 255, 255, 0.03); + } + } + + .user-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; + border: 2px solid var(--bg); + box-shadow: 0 0 0 1px var(--border); + transition: transform 0.2s ease; + } + + &:hover .user-avatar { + transform: scale(1.06); + } + + .user-details { + display: flex; + flex-direction: column; + text-align: left; + + .user-name { + font-size: 13px; + font-weight: 600; + color: var(--text-h); + line-height: 1.2; + } + + .user-role { + font-size: 11px; + color: var(--text); + line-height: 1.2; + margin-top: 1px; + } + } + } + + .logout-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 0; + border: none; + background: none; + font-size: 13px; + font-weight: 600; + cursor: pointer; + + .logout-icon { + font-size: 14px; + } + } + } +} diff --git a/frontend/src/components/chat/ChatWindow.jsx b/frontend/src/components/chat/ChatWindow.jsx new file mode 100644 index 0000000000..5c7c691b61 --- /dev/null +++ b/frontend/src/components/chat/ChatWindow.jsx @@ -0,0 +1,229 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLazyGetMessagesQuery } from '../../api/conversationApi'; +import { + addMessage, + setMessages, + updateMessageStatus, + selectMessages, + selectConversations, +} from '../../redux/slices/chatSlice'; +import { selectCurrentUser } from '../../redux/slices/authSlice'; +import { sendMessage as wsSendMessage } from '../../hooks/useWebSocket'; +import styles from './ChatWindow.module.scss'; + +const SCROLL_THRESHOLD = 80; + +function ChatWindow({ conversationId }) { + const dispatch = useDispatch(); + const currentUser = useSelector(selectCurrentUser); + const conversations = useSelector(selectConversations); + const messages = useSelector((state) => selectMessages(state, conversationId)); + + const [input, setInput] = useState(''); + const [loadingOlder, setLoadingOlder] = useState(false); + const [hasMore, setHasMore] = useState(true); + const [initialLoaded, setInitialLoaded] = useState(false); + + const messagesRef = useRef(null); + const isAtBottomRef = useRef(true); + const prevScrollHeightRef = useRef(0); + + const [fetchMessages, { isFetching }] = useLazyGetMessagesQuery(); + + const conversation = conversations.find((c) => c.id === conversationId); + const otherParticipant = conversation?.otherParticipant || { username: 'Chat' }; + + const loadInitialMessages = useCallback(async () => { + try { + const result = await fetchMessages({ conversationId, limit: 20 }).unwrap(); + dispatch(setMessages({ conversationId, messages: result || [] })); + setHasMore((result || []).length >= 20); + setInitialLoaded(true); + } catch (err) { + console.error('Failed to load messages', err); + setInitialLoaded(true); + } + }, [conversationId, dispatch, fetchMessages]); + + useEffect(() => { + setInitialLoaded(false); + setHasMore(true); + loadInitialMessages(); + }, [conversationId, loadInitialMessages]); + + const scrollToBottom = useCallback((behavior = 'smooth') => { + const el = messagesRef.current; + if (el) { + el.scrollTo({ top: el.scrollHeight, behavior }); + } + }, []); + + useEffect(() => { + if (initialLoaded) { + scrollToBottom('auto'); + } + }, [initialLoaded, conversationId, scrollToBottom]); + + useEffect(() => { + if (isAtBottomRef.current && messages.length) { + scrollToBottom(); + } + }, [messages.length, scrollToBottom]); + + const handleScroll = async () => { + const el = messagesRef.current; + if (!el) return; + + const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight; + isAtBottomRef.current = distanceFromBottom < SCROLL_THRESHOLD; + + if (el.scrollTop > 20 || loadingOlder || !hasMore || isFetching) return; + + const oldest = messages[0]; + if (!oldest?.createdAt) return; + + setLoadingOlder(true); + prevScrollHeightRef.current = el.scrollHeight; + + try { + const older = await fetchMessages({ + conversationId, + cursor: oldest.createdAt, + limit: 20, + }).unwrap(); + + if (!older?.length) { + setHasMore(false); + } else { + dispatch(setMessages({ conversationId, messages: older, prepend: true })); + setHasMore(older.length >= 20); + } + } catch (err) { + console.error('Failed to load older messages', err); + } finally { + setLoadingOlder(false); + requestAnimationFrame(() => { + const container = messagesRef.current; + if (container) { + container.scrollTop = container.scrollHeight - prevScrollHeightRef.current; + } + }); + } + }; + + const submitMessage = useCallback( + (content, retryTempId) => { + const trimmed = content.trim(); + if (!trimmed) return; + + const tempId = retryTempId || `temp-${Date.now()}-${Math.random().toString(36).slice(2)}`; + + if (!retryTempId) { + dispatch( + addMessage({ + tempId, + id: tempId, + conversationId, + senderId: currentUser.id, + content: trimmed, + venueId: null, + isRead: true, + createdAt: new Date().toISOString(), + status: 'sending', + }) + ); + } else { + dispatch(updateMessageStatus({ conversationId, tempId, status: 'sending' })); + } + + const sent = wsSendMessage(conversationId, trimmed); + if (!sent) { + dispatch(updateMessageStatus({ conversationId, tempId, status: 'failed' })); + } + + isAtBottomRef.current = true; + }, + [conversationId, currentUser?.id, dispatch] + ); + + const handleSubmit = (e) => { + e.preventDefault(); + if (!input.trim()) return; + submitMessage(input); + setInput(''); + }; + + const handleKeyDown = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSubmit(e); + } + }; + + const handleRetry = (msg) => { + submitMessage(msg.content, msg.tempId); + }; + + return ( +
+
+

{otherParticipant.username}

+
+ +
+ {(loadingOlder || isFetching) && !initialLoaded && ( +
Loading messages…
+ )} + {loadingOlder && initialLoaded && ( +
Loading older messages…
+ )} + + {messages.map((msg) => { + const isOwn = msg.senderId === currentUser?.id; + const statusClass = + msg.status === 'failed' ? styles.failed : msg.status === 'sending' ? styles.sending : ''; + + return ( +
+
+
+ {msg.content} +
+ {msg.status === 'failed' && ( +
+ Failed to send + +
+ )} +
+
+ ); + })} +
+ +
typing…
+ +
+