diff --git a/.github/workflows/cd-backend.yml b/.github/workflows/cd-backend.yml index b700594..c780e52 100644 --- a/.github/workflows/cd-backend.yml +++ b/.github/workflows/cd-backend.yml @@ -54,4 +54,4 @@ jobs: run: | cd output vercel pull --yes --environment=production --token=$VERCEL_TOKEN - vercel deploy --prod --token=$VERCEL_TOKEN + vercel deploy --prod --token=$VERCEL_TOKEN --force diff --git a/.github/workflows/cd-mobile.yml b/.github/workflows/cd-mobile.yml new file mode 100644 index 0000000..51e355f --- /dev/null +++ b/.github/workflows/cd-mobile.yml @@ -0,0 +1,46 @@ +name: CD - Deploy Mobile + +on: + workflow_dispatch: + inputs: + run_id: + description: "Mobile CI workflow run_id to deploy" + required: true + type: string + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download artifact from CI workflow + env: + GH_TOKEN: ${{ secrets.PAT_TOKEN }} + run: | + echo "Fetching mobile artifact from CI run ID: ${{ github.event.inputs.run_id }}" + gh run download ${{ github.event.inputs.run_id }} --name mobile-dist-${{ github.event.inputs.run_id }} + ls -l + + - name: Extract artifact + run: | + tar -xzf mobile-dist-${{ github.event.inputs.run_id }}.tar.gz + ls -la + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Vercel CLI + run: npm install -g vercel + + - name: Deploy mobile to Vercel + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID_MOBILE }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_MOBILE }} + run: | + vercel deploy dist --prod --yes --token=$VERCEL_TOKEN \ No newline at end of file diff --git a/.github/workflows/deploy-mobile.yml b/.github/workflows/deploy-mobile.yml new file mode 100644 index 0000000..0906873 --- /dev/null +++ b/.github/workflows/deploy-mobile.yml @@ -0,0 +1,63 @@ +name: Deploy Mobile to Vercel +on: + workflow_dispatch: + pull_request: + paths: + - "shatter-mobile/**" + +jobs: + build-check: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: shatter-mobile/package-lock.json + + - name: Install dependencies + working-directory: shatter-mobile + run: npm ci + + - name: Build check only (no deploy) + working-directory: shatter-mobile + env: + EXPO_PUBLIC_API_URL: https://techstart-shatter-backend.vercel.app + run: npx expo export --platform web + + deploy: + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: shatter-mobile/package-lock.json + + - name: Install dependencies + working-directory: shatter-mobile + run: npm ci + + - name: Install Vercel CLI + run: npm install -g vercel + + - name: Build and Deploy to Vercel + working-directory: shatter-mobile + env: + EXPO_PUBLIC_API_URL: https://techstart-shatter-backend.vercel.app + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID_MOBILE }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_MOBILE }} + run: | + npx expo export --platform web + vercel deploy --prod --yes --token=$VERCEL_TOKEN \ No newline at end of file diff --git a/shatter-backend/api/index.ts b/shatter-backend/api/index.ts index 655999c..b596d4d 100644 --- a/shatter-backend/api/index.ts +++ b/shatter-backend/api/index.ts @@ -1,71 +1,13 @@ import 'dotenv/config'; -import mongoose from 'mongoose'; -import app from '../src/app'; +import { connectDB } from '../src/utils/db.js'; +import app from '../src/app.js'; const MONGODB_URI = process.env.MONGO_URI; - -let connectionPromise: Promise | null = null; - -async function connectDB() { - if (mongoose.connection.readyState === 1) { - return; - } - - if (mongoose.connection.readyState !== 0) { - await mongoose.disconnect(); - connectionPromise = null; - } - - if (connectionPromise) { - return connectionPromise; - } - - if (!MONGODB_URI) { - throw new Error('MONGO_URI is not set in environment variables'); - } - - connectionPromise = (async () => { - try { - await mongoose.connect(MONGODB_URI, { - maxPoolSize: 10, - minPoolSize: 2, - maxIdleTimeMS: 30000, - serverSelectionTimeoutMS: 5000, - socketTimeoutMS: 45000, - }); - - mongoose.connection.on('error', (err) => { - console.error('MongoDB connection error:', err); - connectionPromise = null; - }); - - mongoose.connection.on('disconnected', () => { - console.log('MongoDB disconnected'); - connectionPromise = null; - }); - - } catch (error) { - console.error('Failed to connect to MongoDB:', error); - connectionPromise = null; - throw error; - } - })(); - - return connectionPromise; +if (!MONGODB_URI) { + throw new Error('MONGO_URI is not set in environment variables'); } -connectDB().catch(console.error); - -app.use(async (req, res, next) => { - try { - await connectDB(); - next(); - } catch (error: any) { - res.status(500).json({ - error: 'Database connection failed', - message: error.message - }); - } -}); +// Eagerly start connection at module load (Vercel cold start) +connectDB(MONGODB_URI).catch(console.error); -export default app; \ No newline at end of file +export default app; diff --git a/shatter-backend/docs/API_REFERENCE.md b/shatter-backend/docs/API_REFERENCE.md index f1f50a8..a51ac2f 100644 --- a/shatter-backend/docs/API_REFERENCE.md +++ b/shatter-backend/docs/API_REFERENCE.md @@ -1,48 +1,61 @@ # Shatter Backend — API Reference -**Last updated:** 2026-03-01 +**Last updated:** 2026-03-08 **Base URL:** `http://localhost:4000/api` --- ## Table of Contents -- [General Information](#general-information) -- [Endpoint Summary](#endpoint-summary) -- [Authentication (`/api/auth`)](#authentication-apiauth) - - [POST /api/auth/signup](#post-apiauthsignup) - - [POST /api/auth/login](#post-apiauthlogin) - - [GET /api/auth/linkedin](#get-apiauthlinkedin) - - [GET /api/auth/linkedin/callback](#get-apiauthlinkedincallback) - - [POST /api/auth/exchange](#post-apiauthexchange) -- [Users (`/api/users`)](#users-apiusers) - - [GET /api/users](#get-apiusers) - - [POST /api/users](#post-apiusers) - - [GET /api/users/me](#get-apiusersme) - - [GET /api/users/:userId](#get-apiusersuserid) - - [GET /api/users/:userId/events](#get-apiusersuseриdevents) - - [PUT /api/users/:userId](#put-apiusersuserid) -- [Events (`/api/events`)](#events-apievents) - - [POST /api/events/createEvent](#post-apieventscreateevent) - - [GET /api/events/event/:joinCode](#get-apieventseventjoincode) - - [GET /api/events/:eventId](#get-apieventseventid) - - [PUT /api/events/:eventId/status](#put-apieventseventidstatus) - - [POST /api/events/:eventId/join/user](#post-apieventseventиdjoinuser) - - [POST /api/events/:eventId/join/guest](#post-apieventseventиdjoinguest) - - [GET /api/events/createdEvents/user/:userId](#get-apieventscreatedeventsuseriduserid) -- [Bingo (`/api/bingo`)](#bingo-apibingo) - - [POST /api/bingo/createBingo](#post-apibingocreatebingo) - - [GET /api/bingo/getBingo/:eventId](#get-apibingogetbingoeventid) - - [PUT /api/bingo/updateBingo](#put-apibingoupdatebingo) -- [Participant Connections (`/api/participantConnections`)](#participant-connections-apiparticipantconnections) - - [POST /api/participantConnections/](#post-apiparticipantconnections) - - [POST /api/participantConnections/by-emails](#post-apiparticipantconnectionsby-emails) - - [DELETE /api/participantConnections/delete](#delete-apiparticipantconnectionsdelete) - - [GET /api/participantConnections/getByParticipantAndEvent](#get-apiparticipantconnectionsgetbyparticipantandevent) - - [GET /api/participantConnections/getByUserEmailAndEvent](#get-apiparticipantconnectionsgetbyuseremailandevent) - - [GET /api/participantConnections/connected-users](#get-apiparticipantconnectionsconnected-users) -- [Planned Endpoints](#planned-endpoints-) -- [Quick Start Examples](#quick-start-examples) +- [Shatter Backend — API Reference](#shatter-backend--api-reference) + - [Table of Contents](#table-of-contents) + - [Endpoint Summary](#endpoint-summary) + - [General Information](#general-information) + - [Authentication](#authentication) + - [Response Format](#response-format) + - [Common Status Codes](#common-status-codes) + - [Authentication (`/api/auth`)](#authentication-apiauth) + - [POST `/api/auth/signup`](#post-apiauthsignup) + - [POST `/api/auth/login`](#post-apiauthlogin) + - [GET `/api/auth/linkedin`](#get-apiauthlinkedin) + - [GET `/api/auth/linkedin/callback`](#get-apiauthlinkedincallback) + - [POST `/api/auth/exchange`](#post-apiauthexchange) + - [Users (`/api/users`)](#users-apiusers) + - [GET `/api/users`](#get-apiusers) + - [POST `/api/users`](#post-apiusers) + - [GET `/api/users/me`](#get-apiusersme) + - [GET `/api/users/:userId`](#get-apiusersuserid) + - [GET `/api/users/:userId/events`](#get-apiusersuseridevents) + - [PUT `/api/users/:userId`](#put-apiusersuserid) + - [Events (`/api/events`)](#events-apievents) + - [POST `/api/events/createEvent`](#post-apieventscreateevent) + - [GET `/api/events/event/:joinCode`](#get-apieventseventjoincode) + - [GET `/api/events/:eventId`](#get-apieventseventid) + - [PUT `/api/events/:eventId/status`](#put-apieventseventidstatus) + - [POST `/api/events/:eventId/join/user`](#post-apieventseventidjoinuser) + - [POST `/api/events/:eventId/join/guest`](#post-apieventseventidjoinguest) + - [GET `/api/events/createdEvents/user/:userId`](#get-apieventscreatedeventsuseruserid) + - [Bingo (`/api/bingo`)](#bingo-apibingo) + - [POST `/api/bingo/createBingo`](#post-apibingocreatebingo) + - [GET `/api/bingo/getBingo/:eventId`](#get-apibingogetbingoeventid) + - [PUT `/api/bingo/updateBingo`](#put-apibingoupdatebingo) + - [POST `/api/bingo/generate`](#post-apibingogenerate) + - [Participant Connections (`/api/participantConnections`)](#participant-connections-apiparticipantconnections) + - [POST `/api/participantConnections/`](#post-apiparticipantconnections) + - [POST `/api/participantConnections/by-emails`](#post-apiparticipantconnectionsby-emails) + - [DELETE `/api/participantConnections/delete`](#delete-apiparticipantconnectionsdelete) + - [GET `/api/participantConnections/getByParticipantAndEvent`](#get-apiparticipantconnectionsgetbyparticipantandevent) + - [GET `/api/participantConnections/getByUserEmailAndEvent`](#get-apiparticipantconnectionsgetbyuseremailandevent) + - [GET `/api/participantConnections/connected-users`](#get-apiparticipantconnectionsconnected-users) + - [Planned Endpoints ⏳](#planned-endpoints-) + - [Quick Start Examples](#quick-start-examples) + - [1. Sign up](#1-sign-up) + - [2. Log in](#2-log-in) + - [3. Create an event](#3-create-an-event) + - [4. Join the event (as authenticated user)](#4-join-the-event-as-authenticated-user) + - [5. Join the event (as guest)](#5-join-the-event-as-guest) + - [6. Create a bingo game for the event](#6-create-a-bingo-game-for-the-event) + - [7. Get the bingo game](#7-get-the-bingo-game) --- @@ -169,7 +182,8 @@ Create a new user account. | 400 | `"name, email and password are required"` | | 400 | `"Invalid email format"` | | 400 | `"Password must be at least 8 characters long"` | -| 409 | `"Email already exists"` | +| 409 | `"Email already exists"` (local account) | +| 409 | `"This email is associated with a LinkedIn account. Please log in with LinkedIn."` (LinkedIn account) | --- @@ -420,12 +434,18 @@ Get all events a user has joined (populates event details). "joinCode": "12345678", "startDate": "2025-02-01T18:00:00.000Z", "endDate": "2025-02-01T21:00:00.000Z", - "currentState": "In Progress" + "currentState": "In Progress", + "participantIds": [ + { "_id": "666b...", "name": "John Doe", "userId": "664f..." }, + { "_id": "666c...", "name": "Jane Smith", "userId": "664e..." } + ] } ] } ``` +**Note:** Each event's `participantIds` is populated with participant `name` and `userId` fields, enabling the frontend to load participant connections. + **Error Responses:** | Status | Error | @@ -456,6 +476,8 @@ Update a user's profile. Users can only update their own profile. | `bio` | string | | | `profilePhoto` | string | URL | | `socialLinks` | object | `{ linkedin?, github?, other? }` | +| `organization` | string | Where the user works/studies | +| `title` | string | Job title or role | **Success Response (200):** @@ -716,13 +738,13 @@ Join an event as a registered (authenticated) user. | 404 | `"User not found"` | | 404 | `"Event not found"` | | 409 | `"User already joined"` | -| 409 | `"This name is already taken in this event"` | **Special Behavior:** - Creates a Participant record linking user to event +- If the display name is already taken in the event, a `#XXX` suffix is automatically appended (e.g., `John` becomes `John#472`). The response `participant.name` reflects the final display name. - Adds participant to event's `participantIds` array - Adds event to user's `eventHistoryIds` array -- Triggers Pusher event `participant-joined` on channel `event-{eventId}` with payload `{ participantId, name }` +- Triggers Pusher event `participant-joined` on channel `event-{eventId}` with payload `{ participantId, name }` (using the final display name) --- @@ -740,9 +762,18 @@ Join an event as a guest (no account required). **Request Body:** -| Field | Type | Required | -|--------|--------|----------| -| `name` | string | Yes | +| Field | Type | Required | Description | +|--------|--------|----------|-------------| +| `name` | string | Yes | Display name | +| `email` | string | No* | Email address | +| `socialLinks` | object | No* | Social links | +| `socialLinks.linkedin` | string | No | LinkedIn URL | +| `socialLinks.github` | string | No | GitHub URL | +| `socialLinks.other` | string | No | Other URL | +| `organization` | string | No* | Where the guest works/studies | +| `title` | string | No | Job title or role | + +\* At least one of the following is required: `email`, a non-empty field in `socialLinks`, or `organization`. **Success Response (200):** @@ -765,15 +796,18 @@ Join an event as a guest (no account required). | Status | Error | |--------|-------| | 400 | `"Missing fields: guest name and eventId are required"` | +| 400 | `"At least one contact method (email or social link) or organization is required"` | +| 400 | `"Invalid email format"` | | 400 | `"Event is full"` | | 404 | `"Event not found"` | -| 409 | `"This name is already taken in this event"` | +| 409 | `"A user with this email already exists"` | **Special Behavior:** -- Creates a guest User (`authProvider: 'guest'`, no email/password) +- Creates a guest User (`authProvider: 'guest'`) with the provided contact info (email, social links, and/or organization) +- If the display name is already taken in the event, a `#XXX` suffix is automatically appended (e.g., `John` becomes `John#472`). The response `participant.name` reflects the final display name, and the guest User's name is updated to match. - Returns a JWT so the guest can make authenticated requests - Guest can later upgrade to a full account via `PUT /api/users/:userId` -- Triggers Pusher event `participant-joined` on channel `event-{eventId}` with payload `{ participantId, name }` +- Triggers Pusher event `participant-joined` on channel `event-{eventId}` with payload `{ participantId, name }` (using the final display name) --- @@ -827,9 +861,9 @@ Create a bingo game for an event. | Field | Type | Required | Notes | |---------------|------------|----------|-------| -| `_eventId` | ObjectId | Yes | Must reference an existing event | -| `description` | string | No | | -| `grid` | string[][] | No | 2D array of strings | +| `_eventId` | ObjectId | Yes | Must reference an existing event | +| `description` | string | No | | +| `grid` | BingoTile\[\]\[\] | No | 2D array of `{ question: string, shortQuestion: string }` | **Success Response (201):** @@ -842,9 +876,16 @@ Create a bingo game for an event. "_eventId": "665a...", "description": "Networking Bingo", "grid": [ - ["Has a pet", "Speaks 3 languages", "Loves hiking"], - ["Works remotely", "Free space", "Plays guitar"], - ["From another country", "Has a blog", "Codes in Rust"] + [ + { "question": "Has a pet", "shortQuestion": "Has pet" }, + { "question": "Speaks 3 languages", "shortQuestion": "Speaks languages" }, + { "question": "Loves hiking outdoors", "shortQuestion": "Loves hiking" } + ], + [ + { "question": "Works remotely full-time", "shortQuestion": "Works remotely" }, + { "question": "Free space", "shortQuestion": "Free space" }, + { "question": "Plays guitar regularly", "shortQuestion": "Plays guitar" } + ] ] } } @@ -856,7 +897,7 @@ Create a bingo game for an event. |--------|-------| | 400 | `"_eventId is required"` | | 400 | `"_eventId must be a valid ObjectId"` | -| 400 | `"grid must be a 2D array of strings"` | +| 400 | `"grid must be a 2D array of { question: string, shortQuestion: string }"` | | 404 | `"Event not found"` | --- @@ -882,7 +923,12 @@ Get bingo by event ID (or bingo ID). "_id": "bingo_a1b2c3d4", "_eventId": "665a...", "description": "Networking Bingo", - "grid": [["Has a pet", "Speaks 3 languages", ...], ...] + "grid": [ + [ + { "question": "Has a pet", "shortQuestion": "Has pet" }, + { "question": "Speaks 3 languages", "shortQuestion": "Speaks languages" } + ] + ] } } ``` @@ -905,9 +951,9 @@ Update a bingo game. | Field | Type | Required | Notes | |---------------|------------|----------|-------| -| `id` | string | Yes | Bingo `_id` or event `_eventId` | -| `description` | string | No | | -| `grid` | string[][] | No | 2D array of strings | +| `id` | string | Yes | Bingo `_id` or event `_eventId` | +| `description` | string | No | | +| `grid` | BingoTile\[\]\[\] | No | 2D array of `{ question: string, shortQuestion: string }` | **Success Response (200):** @@ -924,7 +970,7 @@ Update a bingo game. |--------|-------| | 400 | `"id is required"` | | 400 | `"description must be a string"` | -| 400 | `"grid must be a 2D array of strings"` | +| 400 | `"grid must be a 2D array of { question: string, shortQuestion: string }"` | | 400 | `"Nothing to update: provide description and/or grid"` | | 404 | `"Bingo not found"` | @@ -932,6 +978,59 @@ Update a bingo game. --- +### POST `/api/bingo/generate` + +Generate an AI-powered bingo grid based on a given context. + +- **Auth:** Protected + +**Request Body:** + +| Field | Type | Required | Notes | +|-----------|--------|----------|-------| +| `context` | string | Yes | Context used to generate bingo content | +| `n_rows` | number | Yes | Number of rows (1–5) | +| `n_cols` | number | Yes | Number of columns (1–5) | + +**Example Request:** + +```json +{ + "context": "Software engineer networking event where developers meet, discuss tech stacks, exchange ideas, talk about startups, open source, AI, and career opportunities", + "n_rows": 2, + "n_cols": 2 +} +``` + +**Example Response:** +``` +{ + "status": true, + "bingo_grid": [ + [ + { + "question": "Sketches architecture on a napkin", + "shortQuestion": "Napkin architecture" + }, + { + "question": "Shows a product demo on phone", + "shortQuestion": "Phone product demo" + } + ], + [ + { + "question": "Explains their open-source contribution", + "shortQuestion": "Open-source contribution" + }, + { + "question": "Asks 'What's your current stack?'", + "shortQuestion": "Current stack question" + } + ] + ] +} +``` + ## Participant Connections (`/api/participantConnections`) ### POST `/api/participantConnections/` @@ -1242,7 +1341,8 @@ curl -X POST http://localhost:4000/api/events//join/user \ curl -X POST http://localhost:4000/api/events//join/guest \ -H "Content-Type: application/json" \ -d '{ - "name": "Guest User" + "name": "Guest User", + "email": "guest@example.com" }' ``` diff --git a/shatter-backend/docs/DATABASE_SCHEMA.md b/shatter-backend/docs/DATABASE_SCHEMA.md index 97b840b..e68af11 100644 --- a/shatter-backend/docs/DATABASE_SCHEMA.md +++ b/shatter-backend/docs/DATABASE_SCHEMA.md @@ -1,6 +1,6 @@ # Shatter Backend — Database Schema Reference -**Last updated:** 2026-03-01 +**Last updated:** 2026-03-08 **Database:** MongoDB with Mongoose ODM **Collections:** 6 @@ -63,6 +63,8 @@ | `passwordHash` | String | No | — | `select: false` — excluded from queries by default | | `linkedinId` | String | No | — | Unique (sparse) | | `linkedinUrl` | String | No | — | Unique (sparse) | +| `organization` | String | No | — | Trimmed | +| `title` | String | No | — | Trimmed | | `bio` | String | No | — | Trimmed | | `profilePhoto` | String | No | — | | | `socialLinks` | Object | No | — | `{ linkedin?: String, github?: String, other?: String }` | @@ -157,6 +159,7 @@ ### Key Behaviors - The compound unique index on `(eventId, name)` is case-insensitive, so "John" and "john" are treated as the same name within an event. +- When a name collision occurs during join, the backend automatically appends a random `#XXX` suffix (e.g., `John#472`) and retries, allowing multiple participants with the same base name. - No timestamps are enabled on this model. --- @@ -174,7 +177,7 @@ | `_id` | String | Auto | Auto | Custom: `bingo_<8 random chars>` | | `_eventId` | ObjectId | Yes | — | Refs `Event` | | `description` | String | No | — | | -| `grid` | [[String]] | No | — | 2D array of strings | +| `grid` | [[{ question: String, shortQuestion: String }]] | No | — | 2D array of BingoTile objects | ### Pre-Save Hooks diff --git a/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md b/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md index 987aeea..07641e4 100644 --- a/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md +++ b/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md @@ -1,6 +1,6 @@ # Shatter Backend — Real-Time Events Guide -**Last updated:** 2026-03-01 +**Last updated:** 2026-03-08 --- @@ -106,7 +106,7 @@ Each event has its own channel. Subscribe when a user enters an event, unsubscri | Field | Type | Description | |-----------------|----------|-------------| | `participantId` | ObjectId | The new participant's ID | -| `name` | string | The participant's display name | +| `name` | string | The participant's display name (may include a `#XXX` suffix if the name was already taken in the event, e.g., `"John#472"`) | **Use case:** Update the live participant list in the event lobby/dashboard without polling. diff --git a/shatter-backend/docs/name-bingo-roadmap.md b/shatter-backend/docs/name-bingo-roadmap.md new file mode 100644 index 0000000..ede1783 --- /dev/null +++ b/shatter-backend/docs/name-bingo-roadmap.md @@ -0,0 +1,1082 @@ +# Name Bingo Backend Roadmap + +This roadmap outlines backend development tasks for the Name Bingo feature. Tasks are organized by priority and dependency order. + +**Scope**: Backend API development only. Frontend (mobile/web) tasks are out of scope but noted as dependencies. + +--- + +## Verification Report + +This roadmap has been cross-referenced against: +- `feature_list.md` (MVP requirements) +- `bingo_walkthrough.md` (user flow requirements) +- Mobile app implementation (`mobile-guest-refactor` branch) + +### Coverage Summary + +| Category | Status | +|----------|--------| +| MVP Backend APIs | ~95% (minor gaps addressed below) | +| Bingo Walkthrough | ~90% (Section 10 gaps addressed below) | +| Data Models | Documented explicitly | +| Lifecycle | Fully covered | +| Real-time | Fully covered | +| Auth | Partially covered (LinkedIn OAuth complete, LinkedIn account linking for guests missing) | +| Participant Connections | Fully implemented (model, CRUD, routes — access control for guests missing) | +| Guest Account Upgrade | Partially covered (email/password upgrade works, LinkedIn linking missing) | +| Mobile Bingo Gameplay | Client-side implementation complete (see Mobile Bingo Implementation Status) | + +### Gaps Addressed in This Version + +| Gap | Resolution | Location | +|-----|------------|----------| +| GET /api/users/:userId (Get Profile) | Added | Phase 3.1 | +| GET /api/users/:userId/current-event | Added | Phase 3.5 | +| profilePhoto in participant search | Added | Phase 6.5 | +| Rate limiting middleware | Added | Phase 6.10 | +| allowFreeTextEntry config | Deferred to Phase 6 | Phase 6.6 | +| gridSize in Bingo model | Deferred to Phase 6 | Phase 6.6 | +| Input sanitization | Added to Phase 4.1 | +| Guest account upgrade via LinkedIn linking | Added | Phase 0.3 Task B | +| Connections access control for guest users | Added | Phase 0.3 Task C | +| Organizer Analytics endpoints (feature_list.md "Wants") | Added as future item | Phase 6.11 | +| `gameType` and `eventImg` fields on Event model | Added | Phase 1.1 | +| `currentState` enum matching mobile `EventState` | Added | Phase 1.2 | +| Event status transition API (priority raised) | Moved from Phase 2 to Phase 1 | Phase 1.3 | + +--- + +## Current Implementation Status + +| Component | Status | Location | +|-----------|--------|----------| +| Authentication (signup/login) | Complete | `auth_controller.ts`, `auth_routes.ts` | +| JWT Middleware | Complete | `auth_middleware.ts`, `jwt_utils.ts` | +| User Model & CRUD | Complete | `user_model.ts`, `user_controller.ts` | +| Event Create/Join/Get | Complete | `event_controller.ts`, `event_model.ts` | +| Participant Model | Complete | `participant_model.ts` | +| Guest Join Flow (with User creation, `#XXX` name suffix on collision) | Complete | `event_controller.ts` | +| Profile Update Endpoint | Complete | `user_controller.ts`, `user_route.ts` | +| Pusher Real-time Setup | Complete | `pusher_websocket.ts` | +| Bingo CRUD (basic) | Complete | `bingo_controller.ts`, `bingo_model.ts` | +| LinkedIn OAuth | Complete | `linkedin_oauth.ts`, `auth_controller.ts` | +| Auth Code Exchange | Complete | `auth_code_model.ts`, `auth_controller.ts` | +| Quick Signup (Guest Join) | Complete | `event_controller.ts`, `user_model.ts` | +| ParticipantConnection Model | Complete | `participant_connection_model.ts` | +| ParticipantConnection CRUD | Complete | `participant_connections_controller.ts`, `participant_connections_routes.ts` | +| Guest Upgrade (Email/Password) | Complete | `user_controller.ts` (`updateUser` — sets password, upgrades authProvider) | +| Documentation (API, Real-time, Schema, Lifecycle) | Complete | `docs/API_REFERENCE.md`, `REALTIME_EVENTS_GUIDE.md`, `DATABASE_SCHEMA.md`, `EVENT_LIFECYCLE.md` | +| QR Code Generation | Complete (web client-side) | `shatter-web/src/components/QRCard.tsx` (uses `qrcode.react`, no backend needed) | +| Guest Upgrade (LinkedIn Linking) | Planned | Phase 0.3 Task B | +| Connections Access Control (Guests) | Planned | Phase 0.3 Task C | +| Event Model (`gameType`, `eventImg`) | Complete | Phase 1.1 | +| Event Status Enum (`currentState`) | Complete | Phase 1.2 | +| Event Status Transition API | Complete | Phase 1.3 | +| Player Game State | Deferred (mobile handles client-side) | Phase 6 | +| Participant Search | Deferred (mobile uses existing participant list) | Phase 6 | +| Event Lifecycle Transitions | Complete | Phase 1.3 | + +--- + +## Mobile Bingo Implementation Status + +The `mobile-guest-refactor` branch implements significant client-side bingo gameplay, which reduces the backend work needed for MVP. The following features are handled entirely on the mobile client: + +| Feature | Mobile Implementation | Backend Dependency | +|---------|----------------------|-------------------| +| Bingo grid display | Renders grid from `GET /api/bingo/getBingo/:eventId` categories | Existing endpoint (works) | +| Name assignment to cards | Autocomplete modal using participant list from event data | Existing `participantIds` on event (works) | +| Duplicate name prevention | Client-side validation — can't assign same name to two cards | None | +| Win detection (rows, cols, diagonals) | Client-side logic checks all win conditions | None | +| Blackout detection & animation | Client-side check for all cells filled + animation | None | +| Game state persistence | AsyncStorage — survives app restart | None | +| Lobby → game transition | Polls event status, transitions when `currentState` changes | **Needs Phase 1.2 + 1.3** | +| Event image display | Renders `eventImg` from event data | **Needs Phase 1.1** | +| Game type display | Renders `gameType` from event data | **Needs Phase 1.1** | + +### Mobile Enum Values (source of truth: `shatter-mobile/src/interfaces/Event.tsx`) + +```typescript +export enum EventState { + UPCOMING = "Upcoming", + IN_PROGRESS = "In Progress", + COMPLETED = "Completed", + INVALID = "Invalid", +} + +export enum GameType { + NAME_BINGO = "Name Bingo" +} +``` + +**Backend must use these exact string values** for `currentState` and `gameType` enums to maintain compatibility. + +--- + +## Phase 0: Authentication Enhancements (Critical Priority) + +These features are specified in `bingo_walkthrough.md` Section 3.1 as primary authentication methods. + +### 0.1 LinkedIn OAuth Integration ✅ COMPLETE + +**Endpoints**: +- `GET /api/auth/linkedin` - Initiates OAuth flow (redirects to LinkedIn) +- `GET /api/auth/linkedin/callback` - OAuth callback (creates/updates user, redirects with auth code) +- `POST /api/auth/exchange` - Exchange single-use auth code for JWT token + +**Implementation** (custom, no Passport dependency): +- `src/utils/linkedin_oauth.ts` - LinkedIn API helpers (`getLinkedInAuthUrl`, `getLinkedInAccessToken`, `getLinkedInProfile`) +- `src/models/auth_code_model.ts` - Single-use auth code model (60s TTL, auto-expires) +- `src/controllers/auth_controller.ts` - `linkedinAuth`, `linkedinCallback`, `exchangeAuthCode` +- `src/routes/auth_routes.ts` - All routes registered + +**User Model Fields** (in `user_model.ts`): +- `linkedinId` (String, unique, sparse) - LinkedIn subject ID +- `linkedinUrl` (String, unique, sparse) - LinkedIn profile URL +- `authProvider` (Enum: 'local' | 'linkedin', default 'local') +- `profilePhoto` (String) - populated from LinkedIn picture + +**Security**: +- CSRF protection via JWT-encoded state token (5-minute expiry) +- Auth code is single-use (atomic `findOneAndDelete`) with 60-second TTL +- JWT token never exposed in redirect URLs +- Email conflict detection (prevents duplicate accounts) + +**Env Vars**: `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, `LINKEDIN_CALLBACK_URL`, `FRONTEND_URL` + +**Frontend Integration**: +1. Open browser to `GET /api/auth/linkedin` +2. User authenticates with LinkedIn +3. Backend redirects to `{FRONTEND_URL}/auth/callback?code=` +4. Frontend calls `POST /api/auth/exchange` with `{ "code": "" }` +5. Response: `{ "message": "Authentication successful", "userId": "...", "token": "..." }` + +--- + +### 0.2 Quick Signup via Guest Join ✅ COMPLETE + +**Approach**: Instead of a separate signup endpoint, guest users are created automatically when joining an event. + +**Endpoint**: `POST /api/events/:eventId/join/guest` +- Takes only `{ name }` in request body +- Creates a User record with `authProvider: 'guest'` (no email/password required) +- Returns `{ success, participant, userId, token }` — guest gets a JWT immediately + +**Profile Completion**: `PUT /api/users/:userId` (protected, self-only) +- Guests can add email, password, bio, profilePhoto, socialLinks later +- Setting a password upgrades `authProvider` from `'guest'` to `'local'` +- Email validated for format and uniqueness, password must be >= 8 chars + +**Model Changes** (`user_model.ts`): +- `email` is now optional with `sparse: true` (allows multiple guest users without email) +- `authProvider` enum: `'local' | 'linkedin' | 'guest'` +- Added `bio` (String) and `socialLinks` (linkedin, github, other) fields + +**Implementation**: +- `src/controllers/event_controller.ts` — upgraded `joinEventAsGuest` +- `src/controllers/user_controller.ts` — added `updateUser` +- `src/routes/user_route.ts` — added `PUT /:userId` route + +**Frontend Dependency**: Mobile app guest join flow + profile completion screen. + +--- + +### 0.3 Guest Account Upgrade Flow (High Priority) + +**Reference**: `bingo_walkthrough.md` Section 10 — Guest Account Completion Flow + +Guest users can join events and play games without interruption, but they cannot access saved connections until they upgrade their account. The backend must support two upgrade paths and enforce connections access control. + +#### Task A: Email/Password Upgrade ✅ COMPLETE + +**Endpoint**: `PUT /api/users/:userId` (existing, protected, self-only) + +Already implemented in `user_controller.ts` (`updateUser`): +- Guest sets `email` + `password` via profile update +- Setting a password automatically upgrades `authProvider` from `'guest'` to `'local'` +- Email validated for format and uniqueness, password must be >= 8 chars + +No additional backend work required. + +#### Task B: LinkedIn Account Linking for Guest Users + +**Endpoint**: `POST /api/auth/linkedin/link` (new, protected) + +**Purpose**: Allow an authenticated guest user to attach their LinkedIn account, upgrading their `authProvider` from `'guest'` to `'linkedin'`. + +**Current gap**: The existing `linkedinCallback` in `auth_controller.ts` rejects requests when an email already exists (duplicate account prevention). It has no logic to link LinkedIn credentials to an existing guest account. + +**Implementation**: +- New endpoint that accepts an authenticated guest user's JWT +- Initiates or completes LinkedIn OAuth and attaches `linkedinId`, `linkedinUrl`, and `profilePhoto` to the existing user +- Updates `authProvider` from `'guest'` to `'linkedin'` +- Alternative approach: Modify the existing `linkedinCallback` to detect when the authenticated user is a guest and link instead of rejecting + +**Request flow**: +1. Authenticated guest user calls `POST /api/auth/linkedin/link` (or is redirected through a linking-specific OAuth flow) +2. Backend exchanges LinkedIn auth code for profile data +3. Backend attaches LinkedIn fields to existing guest user document +4. Returns updated user profile + existing JWT remains valid + +**Security**: +- Only users with `authProvider: 'guest'` can use this endpoint +- LinkedIn account must not already be linked to another user +- Validate JWT and confirm `req.user.userId` matches the account being linked + +**Files to modify/create**: +- `src/controllers/auth_controller.ts` — add `linkLinkedIn` handler (or modify `linkedinCallback`) +- `src/routes/auth_routes.ts` — register new route +- `src/utils/linkedin_oauth.ts` — reuse existing LinkedIn API helpers + +#### Task C: Connections Access Control for Guest Users + +**Purpose**: Enforce that guest users cannot access their connections until they upgrade their account (walkthrough Sections 10.2, 10.5, 10.6). + +**Options** (choose one): + +**Option 1 — Backend enforcement (recommended)**: +- Add middleware or guard check on ParticipantConnection query routes +- If `req.user.authProvider === 'guest'`, return `403 Forbidden` with message: `"Upgrade your account to access connections"` +- Affected routes: + - `GET /api/participantConnections/getByParticipantAndEvent` + - `GET /api/participantConnections/getByUserEmailAndEvent` +- Creating connections is still allowed (connections are made during gameplay), only reading is restricted + +**Option 2 — Frontend-only enforcement**: +- The user profile response already includes `authProvider` +- Frontend checks `authProvider === 'guest'` and shows the upgrade screen instead of connections +- No backend changes needed, but less secure (API still returns data if called directly) + +**Implementation** (Option 1): +- Add a helper function `requireUpgradedAccount` in `src/middleware/` or inline in the controller +- Check user's `authProvider` field from the database (or from JWT payload if added) +- Return 403 with a descriptive error message for guest users + +**Frontend Dependency**: Mobile app "Locked Connections" screen (walkthrough Section 10.5) routes to Account Upgrade Screen. + +--- + +## Phase 1: Event Model Updates & Status API (Critical Priority) + +These features are required for the mobile app to work without mock data. The mobile client depends on `gameType`, `eventImg`, and `currentState` enum values from the Event model, and polls for status transitions in the lobby. + +### 1.1 Add `gameType` and `eventImg` to Event Model ✅ COMPLETE + +**File**: `src/models/event_model.ts` + +**Changes**: +```typescript +gameType: { + type: String, + enum: ['Name Bingo'], + required: true +}, +eventImg: { + type: String, + required: false +} +``` + +**Why**: Mobile renders `gameType` and `eventImg` from event data. Without these fields, the mobile app uses hardcoded fallbacks. + +**Impact on existing endpoints**: +- `POST /api/events/createEvent` — accepts `gameType` (required) and `eventImg` (optional) in request body +- `GET /api/events/:eventId` and `GET /api/events/event/:joinCode` — automatically included in response + +**Frontend Dependency**: Mobile event cards display game type badge and event image. + +--- + +### 1.2 Add Event Status Enum to Event Model ✅ COMPLETE + +**File**: `src/models/event_model.ts` + +**Change**: +```typescript +// Before +currentState: { type: String, required: true } + +// After +currentState: { + type: String, + enum: ['Upcoming', 'In Progress', 'Completed'], + default: 'Upcoming', + required: true +} +``` + +**Important**: These enum values match the mobile app's `EventState` enum exactly (title case with spaces): +- `"Upcoming"` — event created but not started +- `"In Progress"` — event is live, bingo game active +- `"Completed"` — event has ended + +**Migration consideration**: Any existing events with free-form `currentState` values (e.g., `"pending"`, `"active"`) will need to be updated to match the new enum values. Check existing data before applying. + +--- + +### 1.3 Event Status Transition API ✅ COMPLETE + +**Endpoint**: `PUT /api/events/:eventId/status` + +**Request Body**: `{ "status": "In Progress" }` or `{ "status": "Completed" }` + +**Valid Transitions**: +- `Upcoming` → `In Progress` (host starts event) +- `In Progress` → `Completed` (host ends event) + +**Security**: +- Protected route (requires auth) +- Verifies `event.createdBy === req.user.userId` (host only) + +**Side Effects**: +- Emits Pusher `event-started` on channel `event-${eventId}` when transitioning to `In Progress` (payload: `{ status: 'In Progress' }`) +- Emits Pusher `event-ended` on channel `event-${eventId}` when transitioning to `Completed` (payload: `{ status: 'Completed' }`) + +**Implementation**: +- Handler in `src/controllers/event_controller.ts` (`updateEventStatus`) +- Route in `src/routes/event_routes.ts` +- Validates transition is allowed (rejects invalid transitions with 400) +- Returns updated event + +**Frontend Dependency**: Web dashboard Start/End buttons; mobile lobby polls for status change to transition into game. + +--- + +## Phase 2: Real-time Game Events (High Priority) + +### 2.1 Pusher Events for Game State + +| Event | Channel | Payload | Trigger | Status | +|-------|---------|---------|---------|--------| +| `event-started` | `event-${eventId}` | `{ status: 'In Progress' }` | Host starts event (Phase 1.3) | ✅ Complete | +| `event-ended` | `event-${eventId}` | `{ status: 'Completed' }` | Host ends event (Phase 1.3) | ✅ Complete | +| `bingo-achieved` | `event-${eventId}` | `{ participantId, name, type: 'line' \| 'blackout' }` | Player completes line/blackout (future, if server-side game state is added) | Planned | + +**Note**: `event-started` and `event-ended` were implemented as part of Phase 1.3. The `bingo-achieved` event is deferred until server-side game state tracking is implemented (Phase 6), since mobile currently handles win detection client-side. + +--- + +## Phase 3: User & Event Management (Medium Priority) + +### 3.1 User Profile APIs ✅ COMPLETE + +**GET Endpoint**: `GET /api/users/:userId` ✅ +- Returns user profile excluding `passwordHash` +- Protected route (requires auth) +- Located in `user_controller.ts` (`getUserById`) + +**PUT Endpoint**: `PUT /api/users/:userId` ✅ +- **Updatable Fields**: `name`, `email`, `password`, `bio`, `profilePhoto`, `socialLinks` +- **Security**: Protected, self-only (`req.user.userId === req.params.userId` → 403 otherwise) +- **Guest upgrade**: Setting a password upgrades `authProvider` from `'guest'` to `'local'` +- **Validation**: Email format + uniqueness, password >= 8 chars, name cannot be empty +- Located in `user_controller.ts` (`updateUser`), route in `user_route.ts` + +--- + +### 3.2 Leave Event API + +**Endpoint**: `POST /api/events/:eventId/leave` + +**Logic**: +1. Find participant for `req.user.userId` + `eventId` +2. Verify user is not the host (hosts must delete, not leave) +3. Remove participant from `Event.participantIds` +4. Delete `Participant` document +5. Remove event from `User.eventHistoryIds` +6. Emit Pusher `participant-left` event + +--- + +### 3.3 Delete/Cancel Event API + +**Endpoint**: `DELETE /api/events/:eventId` + +**Constraints**: +- Host only (`event.createdBy === req.user.userId`) +- Only when `status === 'Upcoming'` (before event starts) + +**Cascade**: +- Delete all `Participant` documents for this event +- Delete the `Bingo` document +- Remove event from all users' `eventHistoryIds` + +--- + +### 3.4 Event History API ✅ COMPLETE + +**Endpoint**: `GET /api/users/:userId/events` (protected) + +**Purpose**: MVP requirement from `feature_list.md` - "View Previous Events (static list of past events)" + +**Response**: +```json +{ + "success": true, + "events": [ + { + "_id": "665a...", + "name": "Tech Mixer", + "description": "Monthly networking event", + "joinCode": "12345678", + "startDate": "2025-02-01T18:00:00.000Z", + "endDate": "2025-02-01T21:00:00.000Z", + "currentState": "Completed", + "participantIds": [ + { "_id": "666b...", "name": "John Doe", "userId": "664f..." }, + { "_id": "666c...", "name": "Jane Smith", "userId": "664e..." } + ] + } + ] +} +``` + +**Implementation**: +- Populates `eventHistoryIds` from the User document with nested populate on `participantIds` +- Each event includes participant data (`name`, `userId`) to enable loading participant connections +- Located in `user_controller.ts` (`getUserEvents`) + +**Frontend Dependency**: Mobile app "Previous Events" tab (right navigation tab). + +--- + +### 3.5 Get Current Event API + +**Endpoint**: `GET /api/users/:userId/current-event` + +**Purpose**: MVP requirement - "View Current Event" feature + +**Response** (if user is in an active event): +```json +{ + "hasActiveEvent": true, + "event": { + "_id": "...", + "eventName": "Tech Mixer", + "status": "In Progress", + "joinCode": "ABC123", + "participantCount": 25, + "role": "participant" + } +} +``` + +**Response** (if no active event): +```json +{ + "hasActiveEvent": false, + "event": null +} +``` + +**Logic**: +1. Query `Participant` collection for user's participation +2. Join with `Event` collection +3. Filter for `status` in ['Upcoming', 'In Progress'] +4. Return most recent if multiple (edge case) + +**Security**: Protected route, user can only query their own current event + +**Frontend Dependency**: Mobile app needs this to determine if user should see event lobby or join screen. + +--- + +## Phase 4: Validation (Medium Priority) + +### 4.1 Zod Validation Schemas with Sanitization + +**File**: `src/validation/schemas.ts` + +**Schemas to Create**: +- `SignupSchema`, `LoginSchema` +- `CreateEventSchema`, `JoinEventSchema` +- `CreateBingoSchema` +- `UpdateProfileSchema` + +**Input Sanitization** (add to all string fields): +```typescript +import { z } from 'zod'; + +// Sanitization helper +const sanitizeString = (str: string) => str.trim(); + +// Example schema with sanitization +const SignupSchema = z.object({ + name: z.string().min(1).max(100).transform(sanitizeString), + email: z.string().email().transform(s => s.toLowerCase().trim()), + password: z.string().min(8).max(128) +}); +``` + +**Middleware**: `src/middleware/validate.ts` +```typescript +export const validate = (schema: ZodSchema) => (req, res, next) => { + const result = schema.safeParse(req.body); + if (!result.success) return res.status(400).json({ errors: result.error.issues }); + req.body = result.data; // Use sanitized data + next(); +}; +``` + +--- + +## Phase 5: Documentation Tasks ✅ COMPLETE + +All documentation has been created and is located in `shatter-backend/docs/`. + +### 5.1 API_REFERENCE.md ✅ COMPLETE +- Comprehensive endpoint documentation (1,164 lines) +- Covers all implemented endpoints with request/response examples, error codes, and auth requirements +- Located at `docs/API_REFERENCE.md` + +### 5.2 REALTIME_EVENTS_GUIDE.md ✅ COMPLETE +- Pusher setup, channel naming, event payloads, client-side examples +- Located at `docs/REALTIME_EVENTS_GUIDE.md` + +### 5.3 DATABASE_SCHEMA.md ✅ COMPLETE +- All collections with field definitions, indexes, relationships, and pre-save hooks +- Located at `docs/DATABASE_SCHEMA.md` + +### 5.4 EVENT_LIFECYCLE.md ✅ COMPLETE +- State diagram, transition rules, side effects, frontend integration notes +- Located at `docs/EVENT_LIFECYCLE.md` + +--- + +## Phase 6: Polish & Production Ready (Low Priority) + +These are P3 tasks that improve UX and performance but aren't blocking for MVP. Includes tasks deferred from earlier phases because mobile handles them client-side. + +### 6.1 Edit Bingo Square API + +**Endpoint**: Extend `POST /api/bingo/:bingoId/fill-cell` + +**Purpose**: Allow users to change already-filled cells (fix mistakes). + +**Logic**: +- If cell already filled, allow changing the assigned person +- Re-run line detection after change +- Could decrease `completedLines` if editing breaks a line +- Optional: Add event config `allowCellEditing: boolean` + +**Frontend Dependency**: Mobile app cell tap on filled cell shows edit option. + +--- + +### 6.2 Prevent Duplicate Person Assignments (Server-side) + +**Purpose**: Game rule validation (configurable per event). Mobile already handles this client-side for MVP. + +**Implementation**: +- Add to Bingo model: `allowDuplicateAssignments: boolean` (default: true) +- If false, validate `matchedParticipantId` not already used in another cell +- Return 400 error: "You've already assigned {name} to another square" + +--- + +### 6.3 Bingo Leaderboard API + +**Endpoint**: `GET /api/events/:eventId/bingo/leaderboard` + +**Purpose**: Show who completed lines/blackout first. Requires server-side game state (Phase 6.6). + +**Response**: +```json +{ + "leaderboard": [ + { + "rank": 1, + "participantId": "...", + "name": "Alice", + "linesCompleted": 3, + "blackoutAchieved": true, + "blackoutAt": "2024-01-15T14:30:00Z" + } + ] +} +``` + +**Logic**: +- Aggregate all `PlayerBingoState` for the event +- Sort by: `blackoutAchieved` (true first), then `blackoutAt` (earliest first), then `completedLines` (most first) + +**Frontend Dependency**: Optional leaderboard display during/after game. + +--- + +### 6.4 Database Indexes for Performance + +**Purpose**: Optimize queries for scale. + +**Indexes to Create**: + +| Collection | Index | Type | +|------------|-------|------| +| `events` | `joinCode` | unique | +| `events` | `currentState` | regular | +| `events` | `createdBy` | regular | +| `participants` | `(eventId, name)` | compound, unique (case-insensitive collation) | +| `participants` | `eventId` | regular | +| `users` | `email` | unique | +| `users` | `contactLink` | unique, sparse | +| `users` | `linkedinUrl` | unique, sparse | + +**Implementation**: Add to model definitions or create migration script. + +--- + +### 6.5 Participant Search API (Deferred from Phase 1) + +**Note**: Deferred because the mobile app gets participant names from the event's `participantIds` array (populated on `GET /api/events/:eventId`). A dedicated search endpoint is a nice-to-have for large events but not required for MVP. + +**Endpoint**: `GET /api/events/:eventId/participants/search` + +**Query Params**: `?name=` + +**Purpose**: Enable fuzzy name matching for the bingo cell-filling modal (see `bingo_walkthrough.md` Section 7.1). + +**Requirements**: +- Case-insensitive search on `Participant.name` +- Support partial matches (e.g., "joh" matches "John", "Johnny") +- Return max 10 results +- **Include profilePhoto in response** (per bingo_walkthrough 7.1) +- Response: `{ participants: [{ _id, name, profilePhoto }] }` + +**Implementation**: +- Create `src/controllers/participant_controller.ts` +- Create `src/routes/participant_routes.ts` +- Mount at `/api/participants` in `app.ts` +- Use MongoDB `$regex` with `'i'` flag for case-insensitive matching +- Join with User collection to get `profilePhoto` + +**Frontend Dependency**: Mobile app "Who did you find?" modal — currently uses in-memory filtering of participant list. + +--- + +### 6.6 PlayerBingoState Model (Deferred from Phase 1) + +**Note**: Deferred because mobile handles all bingo game state client-side via AsyncStorage for MVP. Server-side state tracking becomes important post-MVP for leaderboards, analytics, and cross-device sync. + +**File**: `src/models/player_bingo_state_model.ts` + +**Schema**: +```typescript +{ + eventId: ObjectId, // ref: Event + bingoId: string, // ref: Bingo + participantId: ObjectId, // ref: Participant (the player) + filledCells: [{ + row: number, + col: number, + matchedParticipantId: ObjectId | null, // null for free-text entries + matchedName: string, + filledAt: Date + }], + completedLines: number, + firstBingoAt: Date | null, + blackoutAt: Date | null, + isLocked: boolean +} +``` + +**Bingo Model Enhancements** (add to existing `bingo_model.ts`): +```typescript +{ + // Existing fields... + gridSize: { + type: Number, + enum: [3, 4, 5], + default: 5, + required: true + }, + prompts: [{ + text: String, // Full prompt text + shortText: String // Abbreviated version (e.g., "Has dog") + }], + allowFreeTextEntry: { + type: Boolean, + default: false // Allow typing names not in participant list + }, + allowDuplicateAssignments: { + type: Boolean, + default: true + } +} +``` + +**Indexes**: +- Compound unique index on `(eventId, participantId)` + +--- + +### 6.7 Cell Fill API (Deferred from Phase 1) + +**Note**: Deferred because mobile manages cell fills locally via AsyncStorage for MVP. + +**Endpoint**: `POST /api/bingo/:bingoId/fill-cell` + +**Request Body**: +```json +{ + "participantId": "player's participant ID", + "row": 0, + "col": 2, + "matchedParticipantId": "matched person's participant ID", + "matchedName": "John Doe" +} +``` + +**Logic**: +1. Validate player is in the event +2. If `matchedParticipantId` provided, validate matched participant exists in event +3. If `matchedParticipantId` is null, verify `allowFreeTextEntry` is true +4. Check cell not already filled +5. Update `PlayerBingoState.filledCells` +6. Run line detection (rows, columns, diagonals) +7. Check for blackout (all cells filled) +8. Record `firstBingoAt` on first line completion +9. Record `blackoutAt` on blackout +10. Emit Pusher event `bingo-achieved` if line/blackout + +--- + +### 6.8 Get Player Bingo State API (Deferred from Phase 1) + +**Note**: Deferred because mobile uses AsyncStorage for game state persistence for MVP. + +**Endpoint**: `GET /api/bingo/:bingoId/state/:participantId` + +**Response**: +```json +{ + "gridSize": 5, + "grid": [ + [{"text": "Has a dog", "shortText": "Has dog"}] + ], + "filledCells": [{ "row": 0, "col": 1, "matchedName": "John", "matchedParticipantId": "..." }], + "completedLines": 0, + "firstBingoAt": null, + "blackoutAt": null, + "isLocked": false, + "allowFreeTextEntry": false +} +``` + +--- + +### 6.9 Error Handling Middleware + +**File**: `src/middleware/error_handler.ts` + +**Purpose**: Consistent error responses across all endpoints. + +**Implementation**: +```typescript +export const errorHandler = (err, req, res, next) => { + console.error(err.stack); + + // Mongoose validation errors + if (err.name === 'ValidationError') { + return res.status(400).json({ + error: 'Validation failed', + details: Object.values(err.errors).map(e => e.message) + }); + } + + // Mongoose duplicate key + if (err.code === 11000) { + return res.status(409).json({ + error: 'Duplicate entry', + field: Object.keys(err.keyPattern)[0] + }); + } + + // JWT errors + if (err.name === 'JsonWebTokenError') { + return res.status(401).json({ error: 'Invalid token' }); + } + + // Default + res.status(500).json({ error: 'Internal server error' }); +}; +``` + +**Mount in `app.ts`**: `app.use(errorHandler)` after all routes. + +--- + +### 6.10 Rate Limiting Middleware + +**File**: `src/middleware/rate_limiter.ts` + +**Purpose**: Protect against brute force attacks and API abuse (feature_list.md "Wants") + +**Implementation**: +```typescript +import rateLimit from 'express-rate-limit'; + +// Strict limiter for auth endpoints +export const authLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 10, // 10 requests per window + message: { error: 'Too many login attempts, please try again later' }, + standardHeaders: true, + legacyHeaders: false +}); + +// General API limiter +export const apiLimiter = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 100, // 100 requests per minute + message: { error: 'Too many requests, please slow down' } +}); +``` + +**Installation**: +```bash +npm install express-rate-limit +npm install -D @types/express-rate-limit +``` + +--- + +### 6.11 Organizer Analytics Endpoints (Low Priority / Future) + +**Reference**: `feature_list.md` "Wants" section — "Organizer Analytics endpoints" + +**Purpose**: Provide organizers with event analytics such as attendance count, average engagement, and participation stats. + +**Potential Endpoints**: +- `GET /api/events/:eventId/analytics` — Returns event-level analytics (protected, host only) + - Participant count, bingo completion rates, average lines completed, blackout count + - Connection counts, most-connected participants + - Time-based metrics (average time to first bingo, event duration) + +**Implementation Notes**: +- Aggregate data from `Participant`, `PlayerBingoState`, and `ParticipantConnection` collections +- Consider caching analytics results for ended events (data won't change) +- Low priority — not blocking MVP + +**Frontend Dependency**: Web organizer dashboard "Event Summary Page" and analytics views. + +--- + +## Sprint Recommendations + +### Sprint 1: Event Model Updates & Status API ✅ COMPLETE +- ~~Task 1.1: Add `gameType` and `eventImg` to Event model~~ ✅ +- ~~Task 1.2: Add `currentState` enum (`Upcoming`, `In Progress`, `Completed`)~~ ✅ +- ~~Task 1.3: Event Status Transition API (`PUT /api/events/:eventId/status`)~~ ✅ + +### Sprint 2: Guest Account Upgrades & Real-time +- Task 0.3B: LinkedIn Account Linking for Guest Users +- Task 0.3C: Connections Access Control for Guests +- ~~Task 2.1: Pusher game events (`event-started`, `event-ended`)~~ ✅ Implemented as part of Phase 1.3 + +### Sprint 3: Event Management +- ~~Task 3.1: User Profile APIs (GET and PUT)~~ ✅ Complete +- Task 3.2: Leave Event API +- Task 3.3: Delete/Cancel Event API +- ~~Task 3.4: Event History API~~ ✅ Complete +- Task 3.5: Get Current Event API + +### Sprint 4: Validation +- Task 4.1: Zod Validation Schemas with Sanitization +- ~~Documentation tasks (5.1-5.4)~~ ✅ Complete + +### Sprint 5: Server-side Game State & Polish (if needed post-MVP) +- Task 6.5: Participant Search API +- Task 6.6: PlayerBingoState Model +- Task 6.7: Cell Fill API +- Task 6.8: Get Player Bingo State API +- Task 6.1: Edit Bingo Square +- Task 6.2: Prevent Duplicate Assignments (server-side) +- Task 6.3: Bingo Leaderboard +- Task 6.4: Database Indexes +- Task 6.9: Error Handling Middleware +- Task 6.10: Rate Limiting Middleware +- Task 6.11: Organizer Analytics Endpoints + +--- + +## Dependencies to Install + +```bash +# Authentication - Already installed: bcryptjs, jsonwebtoken, axios (for LinkedIn OAuth) +# No passport needed - LinkedIn OAuth uses custom implementation with axios + +# Validation +npm install zod + +# Rate Limiting +npm install express-rate-limit +npm install -D @types/express-rate-limit +``` + +--- + +## Data Model Summary + +### User Collection +```typescript +{ + _id: ObjectId, + name: string, + email?: string (unique, sparse index), // optional for guest users + passwordHash?: string (select: false), + linkedinId?: string (unique, sparse index), // LinkedIn subject ID + linkedinUrl?: string (unique, sparse index), + authProvider: 'local' | 'linkedin' | 'guest' (default: 'local'), + bio?: string, + profilePhoto?: string, + socialLinks?: { + linkedin?: string, + github?: string, + other?: string + }, + lastLogin?: Date, + passwordChangedAt?: Date, + eventHistoryIds: ObjectId[], + createdAt: Date, + updatedAt: Date +} +``` + +### AuthCode Collection (temporary, auto-expiring) +```typescript +{ + _id: ObjectId, + code: string (unique, indexed), + userId: ObjectId (ref: User), + createdAt: Date (TTL: 60 seconds) +} +``` + +### Event Collection +```typescript +{ + _id: ObjectId, + name: string, + description: string, + createdBy: ObjectId (ref: User), + joinCode: string (unique), + currentState: 'Upcoming' | 'In Progress' | 'Completed' (default: 'Upcoming'), + gameType: 'Name Bingo' (required), + eventImg?: string, + startDate: Date, + endDate: Date, + maxParticipant: number, + participantIds: ObjectId[] (ref: Participant), + createdAt: Date, + updatedAt: Date +} +``` + +### Participant Collection +```typescript +{ + _id: ObjectId, + eventId: ObjectId (ref: Event, required), + userId: ObjectId | null (ref: User, default: null), // nullable, not required + name: string (required) +} +// Index: (eventId, name) compound unique with case-insensitive collation +// Note: duplicate names get an automatic #XXX suffix (e.g., "John#472") instead of being rejected +// Note: no role or joinedAt fields in current model +``` + +### Bingo Collection +```typescript +{ + _id: string (auto-generated, e.g. "bingo_xxxxxxxx"), + _eventId: ObjectId (ref: Event, required), + description?: string, + grid?: string[][] (2D string array) +} +// Note: gridSize, prompts, allowFreeTextEntry, allowDuplicateAssignments are planned for Phase 6 +``` + +### ParticipantConnection Collection ✅ IMPLEMENTED +```typescript +{ + _id: string (auto-generated, e.g. "participantConnection_xxxxxxxx"), + _eventId: ObjectId (ref: Event, required), + primaryParticipantId: ObjectId (ref: Participant, required), + secondaryParticipantId: ObjectId (ref: Participant, required), + description?: string +} +// Duplicate prevention: checked via query on (_eventId, primaryParticipantId, secondaryParticipantId) +``` + +### PlayerBingoState Collection (Planned — Phase 6) +```typescript +{ + _id: ObjectId, + eventId: ObjectId (ref: Event), + bingoId: ObjectId (ref: Bingo), + participantId: ObjectId (ref: Participant), + filledCells: [{ + row: number, + col: number, + matchedParticipantId: ObjectId | null, + matchedName: string, + filledAt: Date + }], + completedLines: number, + firstBingoAt: Date | null, + blackoutAt: Date | null, + isLocked: boolean +} +// Index: (eventId, participantId) compound unique +``` + +--- + +## API Summary + +### Authentication +``` +POST /api/auth/signup - Email/password OR contact link signup +POST /api/auth/login - Email/password login +GET /api/auth/linkedin - Initiate LinkedIn OAuth ✅ +GET /api/auth/linkedin/callback - LinkedIn OAuth callback ✅ +POST /api/auth/exchange - Exchange auth code for JWT ✅ +POST /api/auth/linkedin/link - Link LinkedIn to existing guest account (protected, guest only) +GET /api/users/me - Get current user (protected) +``` + +### Users +``` +GET /api/users/:userId - Get user profile ✅ +PUT /api/users/:userId - Update user profile (protected, self only) ✅ +GET /api/users/:userId/events - Get user's event history (protected) ✅ +GET /api/users/:userId/current-event - Get user's active event (protected) +``` + +### Events +``` +POST /api/events - Create event (protected) +POST /api/events/join - Join event with joinCode (protected) +GET /api/events/:eventId - Get event details +PUT /api/events/:eventId/status - Update event status (protected, host only) +POST /api/events/:eventId/leave - Leave event (protected) +DELETE /api/events/:eventId - Cancel event (protected, host only, Upcoming only) +GET /api/events/:eventId/participants/search - Search participants (protected, Phase 6) +``` + +### Participant Connections ✅ IMPLEMENTED +``` +POST /api/participantConnections - Create connection by participant IDs (protected) ✅ +POST /api/participantConnections/by-emails - Create connection by user emails (protected) ✅ +DELETE /api/participantConnections/delete - Delete connection (protected) ✅ +GET /api/participantConnections/getByParticipantAndEvent - Get connections by participant & event (protected) ✅ +GET /api/participantConnections/getByUserEmailAndEvent - Get connections by user email & event (protected) ✅ +``` + +### Name Bingo (Phase 6 — server-side game state) +``` +GET /api/bingo/:bingoId/state/:participantId - Get player's board state (protected) +POST /api/bingo/:bingoId/fill-cell - Fill a cell (protected) +GET /api/events/:eventId/bingo/leaderboard - Get leaderboard (protected) +``` diff --git a/shatter-backend/package-lock.json b/shatter-backend/package-lock.json index dd99515..0de0334 100644 --- a/shatter-backend/package-lock.json +++ b/shatter-backend/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@google/genai": "^1.41.0", + "@modelcontextprotocol/sdk": "^1.27.1", "axios": "^1.13.5", "bcryptjs": "^3.0.3", "cors": "^2.8.5", @@ -18,7 +20,8 @@ "mongoose": "^8.19.2", "pusher": "^5.3.2", "socket.io": "^4.8.1", - "zod": "^4.1.12" + "zod": "^4.3.6", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@eslint/js": "^9.38.0", @@ -31,6 +34,7 @@ "globals": "^16.4.0", "jiti": "^2.6.1", "ts-node-dev": "^2.0.0", + "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.46.2" } @@ -48,10 +52,452 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "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": { @@ -91,15 +537,15 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -118,7 +564,7 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "node_modules/@eslint/core": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", @@ -131,34 +577,21 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -181,23 +614,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", - "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -231,17 +651,39 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, + "node_modules/@google/genai": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.45.0.tgz", + "integrity": "sha512-+sNRWhKiRibVgc4OKi7aBJJ0A7RcoVD8tGG+eFkqxAWRjASDW+ktS9lLwTDnAxZICzCVoeAdu8dYLJVTX60N9w==", "license": "Apache-2.0", "dependencies": { - "@types/json-schema": "^7.0.15" + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" } }, "node_modules/@humanfs/core": { @@ -296,6 +738,23 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -324,53 +783,151 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", - "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", "license": "MIT", "dependencies": { - "sparse-bitfield": "^3.0.3" + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">= 8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "sparse-bitfield": "^3.0.3" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "optional": true, "engines": { - "node": ">= 8" + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -378,9 +935,9 @@ "license": "MIT" }, "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", "dev": true, "license": "MIT" }, @@ -450,21 +1007,21 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", - "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^1" + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "dev": true, "license": "MIT", "dependencies": { @@ -499,13 +1056,6 @@ "@types/node": "*" } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -514,9 +1064,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", - "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -533,9 +1083,9 @@ } }, "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", "dev": true, "license": "MIT" }, @@ -546,6 +1096,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -557,25 +1113,13 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -618,22 +1162,30 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", - "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/type-utils": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -643,8 +1195,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.2", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -659,17 +1211,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", - "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -679,20 +1231,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", - "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -706,14 +1258,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", - "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2" + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -724,9 +1276,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", - "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", "dev": true, "license": "MIT", "engines": { @@ -741,17 +1293,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", - "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -761,14 +1313,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", - "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", "dev": true, "license": "MIT", "engines": { @@ -780,22 +1332,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", - "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.2", - "@typescript-eslint/tsconfig-utils": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -808,43 +1359,56 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", - "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -854,26 +1418,39 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", - "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/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/abort-controller": { @@ -902,9 +1479,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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": { @@ -925,9 +1502,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", "dependencies": { @@ -937,10 +1514,19 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -954,11 +1540,61 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -984,6 +1620,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1005,9 +1654,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", @@ -1019,7 +1668,26 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/base64id": { @@ -1040,6 +1708,15 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1054,23 +1731,27 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "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.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "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": { @@ -1209,11 +1890,23 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1226,7 +1919,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1249,15 +1941,16 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -1288,9 +1981,9 @@ } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "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", @@ -1298,6 +1991,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/create-require": { @@ -1311,7 +2008,6 @@ "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", @@ -1322,6 +2018,15 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1365,9 +2070,9 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1375,9 +2080,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -1410,6 +2115,12 @@ "xtend": "^4.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -1425,6 +2136,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1435,20 +2152,21 @@ } }, "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.6.tgz", + "integrity": "sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==", "license": "MIT", "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" + "ws": "~8.18.3" }, "engines": { "node": ">=10.2.0" @@ -1476,23 +2194,6 @@ "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/engine.io/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1523,6 +2224,27 @@ "node": ">= 0.6" } }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "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/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1568,6 +2290,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1588,25 +2352,25 @@ } }, "node_modules/eslint": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", - "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.1", - "@eslint/core": "^0.16.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.38.0", - "@eslint/plugin-kit": "^0.4.0", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -1625,7 +2389,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -1677,19 +2441,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -1709,9 +2460,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "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": { @@ -1772,19 +2523,41 @@ "node": ">=6" } }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "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.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", @@ -1814,30 +2587,36 @@ "url": "https://opencollective.com/express" } }, - "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-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "ip-address": "10.1.0" }, "engines": { - "node": ">=8.6.0" + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "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==", + "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", @@ -1852,14 +2631,61 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" } }, "node_modules/file-entry-cache": { @@ -1889,9 +2715,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "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", @@ -1902,7 +2728,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/find-up": { @@ -1937,9 +2767,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true, "license": "ISC" }, @@ -1963,6 +2793,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -2000,6 +2846,18 @@ "node": ">= 0.6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2049,6 +2907,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2086,45 +2973,81 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "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==", + "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.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", - "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", "engines": { @@ -2134,6 +3057,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.1.tgz", + "integrity": "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "7.1.3", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2146,13 +3095,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2202,41 +3144,62 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", + "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "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.1", - "toidentifier": "1.0.1" + "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/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, "engines": { - "node": ">= 0.8" + "node": ">= 14" } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "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": { @@ -2294,6 +3257,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2352,6 +3324,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2381,13 +3362,41 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "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/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -2398,6 +3407,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2411,6 +3429,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2425,6 +3452,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "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", @@ -2433,12 +3466,12 @@ "license": "MIT" }, "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "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": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -2455,9 +3488,9 @@ } }, "node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "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", @@ -2466,12 +3499,12 @@ } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "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": "^1.4.1", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -2573,6 +3606,18 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2616,30 +3661,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -2650,21 +3671,25 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "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": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2684,6 +3709,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -2697,7 +3731,100 @@ "node": ">=10" } }, - "node_modules/mongodb": { + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.0.tgz", + "integrity": "sha512-Bul4Ha6J8IqzFrb0B1xpVzkC3S0sk43dmLSnhFOn8eJlZiLwL5WO6cRymmjaADdCMjUcCpj2ce8hZI6O4ZFSug==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/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", + "optional": true, + "peer": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/mongoose/node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/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", + "optional": true, + "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mongoose/node_modules/mongodb": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", @@ -2738,41 +3865,59 @@ "snappy": { "optional": true }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" + "socks": { + "optional": true + } } }, - "node_modules/mongoose": { - "version": "8.19.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.19.2.tgz", - "integrity": "sha512-ww2T4dBV+suCbOfG5YPwj9pLCfUVyj8FEA1D3Ux1HHqutpLxGyOYEPU06iPRBW4cKr3PJfOSYsIpHWPTkz5zig==", + "node_modules/mongoose/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "bson": "^6.10.4", - "kareem": "2.6.3", - "mongodb": "~6.20.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=16.20.1" + "node": "4.x || >=6.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/mongoose/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true, + "peer": true + }, + "node_modules/mongoose/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "node_modules/mpath": { @@ -2818,46 +3963,42 @@ "node": ">= 0.6" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=10.5.0" } }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/normalize-path": { @@ -2962,6 +4103,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3008,7 +4168,6 @@ "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" @@ -3021,6 +4180,22 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -3032,18 +4207,27 @@ } }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3054,6 +4238,30 @@ "node": ">= 0.8.0" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3099,10 +4307,52 @@ "node": ">= 4.0.0" } }, + "node_modules/pusher/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pusher/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/pusher/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/pusher/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -3114,27 +4364,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "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/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3145,36 +4374,20 @@ } }, "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "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.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "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/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3188,6 +4401,28 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -3219,29 +4454,38 @@ "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "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/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">= 4" } }, "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/router": { @@ -3257,31 +4501,7 @@ "path-to-regexp": "^8.0.0" }, "engines": { - "node": ">= 18" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "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", - "dependencies": { - "queue-microtask": "^1.2.2" + "node": ">= 18" } }, "node_modules/safe-buffer": { @@ -3311,9 +4531,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3323,31 +4543,35 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "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.0", - "mime-types": "^3.0.1", + "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.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "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", @@ -3357,6 +4581,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -3369,7 +4597,6 @@ "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" @@ -3382,7 +4609,6 @@ "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" @@ -3466,16 +4692,28 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", - "debug": "~4.3.2", + "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" @@ -3485,62 +4723,49 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", "license": "MIT", "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" + "debug": "~4.4.1", + "ws": "~8.18.3" } }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { - "supports-color": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { "optional": true } } }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/socket.io/node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3554,23 +4779,6 @@ "node": ">= 0.6" } }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/socket.io/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3640,6 +4848,102 @@ "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -3651,13 +4955,16 @@ } }, "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { @@ -3686,6 +4993,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3731,9 +5055,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -3822,6 +5146,42 @@ } } }, + "node_modules/ts-node-dev/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/tsconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", @@ -3835,6 +5195,36 @@ "strip-json-comments": "^2.0.0" } }, + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -3889,16 +5279,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz", - "integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", + "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2" + "@typescript-eslint/eslint-plugin": "8.57.0", + "@typescript-eslint/parser": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3908,7 +5298,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -3953,6 +5343,15 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -3979,7 +5378,6 @@ "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" @@ -4001,6 +5399,94 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4008,9 +5494,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -4062,13 +5548,22 @@ } }, "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/shatter-backend/package.json b/shatter-backend/package.json index a9c6f6f..1d83013 100644 --- a/shatter-backend/package.json +++ b/shatter-backend/package.json @@ -2,9 +2,10 @@ "name": "shatter-backend", "version": "1.0.0", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "ts-node-dev --respawn --transpile-only src/server.ts", + "dev": "tsx watch src/server.ts", "build": "tsc", "start": "node dist/server.js" }, @@ -13,6 +14,8 @@ "license": "ISC", "description": "", "dependencies": { + "@google/genai": "^1.41.0", + "@modelcontextprotocol/sdk": "^1.27.1", "axios": "^1.13.5", "bcryptjs": "^3.0.3", "cors": "^2.8.5", @@ -22,7 +25,8 @@ "mongoose": "^8.19.2", "pusher": "^5.3.2", "socket.io": "^4.8.1", - "zod": "^4.1.12" + "zod": "^4.3.6", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@eslint/js": "^9.38.0", @@ -35,6 +39,7 @@ "globals": "^16.4.0", "jiti": "^2.6.1", "ts-node-dev": "^2.0.0", + "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.46.2" } diff --git a/shatter-backend/src/ai/prompt_builder.ts b/shatter-backend/src/ai/prompt_builder.ts new file mode 100644 index 0000000..c7cb6cc --- /dev/null +++ b/shatter-backend/src/ai/prompt_builder.ts @@ -0,0 +1,74 @@ +/** + * Prompt builder utility class. + * + * Allows incremental construction of a prompt string + * from multiple parts, joined by a configurable separator. + */ +export class Prompt { + + private promptParts: string[]; + private separator: string; + private promptString: string; + + + /** + * Creates a new Prompt instance. + * + * @param arr - Initial array of prompt parts. + * @param separator - String used to separate parts when generating the full prompt. + */ + public constructor(arr: string[] = [], separator: string = "\n\n") { + this.promptParts = arr; + this.separator = separator; + this.promptString = arr.join(this.separator); + } + + /** + * Returns the current array of prompt parts. + * + * @returns Array of prompt segments. + */ + public getPromptParts(): string[] { + return this.promptParts; + } + + /** + * Replaces the current prompt parts with a new array + * and regenerates the full prompt string. + * + * @param arr - New array of prompt segments. + */ + public setPromptParts(arr: string[]): void { + this.promptParts = arr; + this.generatePrompt(); + } + + /** + * Adds a new part to the prompt and regenerates + * the full prompt string. + * + * @param part - A string segment to append to the prompt. + */ + public addPart(part: string): void { + this.promptParts.push(part); + this.generatePrompt(); + } + + /** + * Regenerates the full prompt string by joining + * all prompt parts using the configured separator. + */ + public generatePrompt(): void { + this.promptString = this.promptParts.join(this.separator); + } + + /** + * Returns the fully generated prompt string. + * + * @returns The complete prompt as a single string. + */ + public getPrompt(): string { + return this.promptString; + } + +} diff --git a/shatter-backend/src/ai/prompts/bingo.txt b/shatter-backend/src/ai/prompts/bingo.txt new file mode 100644 index 0000000..644a978 --- /dev/null +++ b/shatter-backend/src/ai/prompts/bingo.txt @@ -0,0 +1,83 @@ +Generate creative bingo entries based on the provided event context. + +Goal +Create short bingo squares that describe recognizable moments, traits, or behaviors of professionals at the event. + +Length +- 3–8 words per entry. +- Keep entries concise and punchy. + +Key Rule: Be Specific +Entries must describe a concrete behavior, situation, or trait someone could realistically notice at the event. + +Avoid vague descriptions of conversation. + +Avoid phrases like: +- "talking about" +- "discusses" +- "mentions" +- "asks" +- "conversation about" +- "debates" +- "connects on" +- "networking with" + +These are too generic and do not describe an observable bingo moment. + +Instead prefer: + +Observable behaviors +Work-related habits +Professional traits +Real situations that happen during networking +Work artifacts or tools someone references or shows + +Good structural patterns + +Action +Shows photos of a recent project +Sketches an idea on a napkin +Explains how their team works + +Situation +Runs into a former coworker +Recognizes someone from online +Arrives straight from work + +Work artifact +Laptop covered in stickers +Shows a product demo +Pulls up slides on their phone + +Professional trait +Always “too busy lately” +Recently started a new role +Proud of their latest project + +Quote +Has said: "It's been a crazy week" +Has said: "We're hiring right now" + +Context usage +Use the provided event context to inspire industry-relevant references when appropriate. + +Examples +Shows photos of a recent project +Sketches an idea while talking +Proud of their latest work +Runs into someone they know +Recently changed jobs +Laptop covered in company stickers +Shows a demo on their phone +Explains a project they built +Has said: "It's been a long week" + +Avoid +- Generic phrases describing conversation +- Things that always happen at networking events (ex: exchanging LinkedIn) +- Overly abstract statements +- Repeating the same structure across many entries + +Diversity rule +Ensure entries vary in structure, wording, and situation. +No more than two entries may begin with the same first word. \ No newline at end of file diff --git a/shatter-backend/src/ai/prompts/bingo_short_questions.txt b/shatter-backend/src/ai/prompts/bingo_short_questions.txt new file mode 100644 index 0000000..8b12320 --- /dev/null +++ b/shatter-backend/src/ai/prompts/bingo_short_questions.txt @@ -0,0 +1,2 @@ +You will generate the bingo grid with shortened version of the questions while maintaining the same structure. +Each entry should be turned into a max 3 word version of the question that describes what the question is about. diff --git a/shatter-backend/src/app.ts b/shatter-backend/src/app.ts index 7f5f7ea..da15a3e 100644 --- a/shatter-backend/src/app.ts +++ b/shatter-backend/src/app.ts @@ -1,11 +1,12 @@ import express from "express"; import cors from "cors"; -import userRoutes from './routes/user_route'; -import authRoutes from './routes/auth_routes'; -import eventRoutes from './routes/event_routes'; -import bingoRoutes from './routes/bingo_routes'; -import participantConnectionRoutes from "./routes/participant_connections_routes"; +import { ensureConnection } from "./utils/db.js"; +import userRoutes from './routes/user_route.js'; +import authRoutes from './routes/auth_routes.js'; +import eventRoutes from './routes/event_routes.js'; +import bingoRoutes from './routes/bingo_routes.js'; +import participantConnectionRoutes from "./routes/participant_connections_routes.js"; const app = express(); @@ -40,10 +41,13 @@ app.get("/", (_req, res) => { res.send("Hello"); }); +// Ensure DB connection is alive before handling any API request +app.use("/api", ensureConnection); + app.use('/api/users', userRoutes); app.use('/api/auth', authRoutes); app.use('/api/events', eventRoutes); app.use('/api/bingo', bingoRoutes); app.use("/api/participantConnections", participantConnectionRoutes); -export default app; \ No newline at end of file +export default app; diff --git a/shatter-backend/src/controllers/auth_controller.ts b/shatter-backend/src/controllers/auth_controller.ts index 7d23c0c..9303990 100644 --- a/shatter-backend/src/controllers/auth_controller.ts +++ b/shatter-backend/src/controllers/auth_controller.ts @@ -1,11 +1,11 @@ import crypto from 'crypto'; import jwt from 'jsonwebtoken'; import { Request, Response } from 'express'; -import { User } from '../models/user_model'; -import { AuthCode } from '../models/auth_code_model'; -import { hashPassword, comparePassword } from '../utils/password_hash'; -import { generateToken } from '../utils/jwt_utils'; -import { getLinkedInAuthUrl, getLinkedInAccessToken, getLinkedInProfile } from '../utils/linkedin_oauth'; +import { User } from '../models/user_model.js'; +import { AuthCode } from '../models/auth_code_model.js'; +import { hashPassword, comparePassword } from '../utils/password_hash.js'; +import { generateToken } from '../utils/jwt_utils.js'; +import { getLinkedInAuthUrl, getLinkedInAccessToken, getLinkedInProfile } from '../utils/linkedin_oauth.js'; const JWT_SECRET = process.env.JWT_SECRET || ''; @@ -57,8 +57,13 @@ export const signup = async (req: Request, res: Response) => { // check if email already exists const existingUser = await User.findOne({ email: normalizedEmail }).lean(); if (existingUser) { + if (existingUser.authProvider === 'linkedin') { + return res.status(409).json({ + error: 'This email is associated with a LinkedIn account. Please log in with LinkedIn.', + }); + } return res.status(409).json({ - error: 'Email already exists' + error: 'Email already exists', }); } diff --git a/shatter-backend/src/controllers/bingo_controller.ts b/shatter-backend/src/controllers/bingo_controller.ts index e094962..0a96455 100644 --- a/shatter-backend/src/controllers/bingo_controller.ts +++ b/shatter-backend/src/controllers/bingo_controller.ts @@ -1,8 +1,105 @@ -// controllers/bingo_controller.ts +// controllers/bingo_controller.js import { Request, Response } from "express"; import { Types } from "mongoose"; -import { Bingo } from "../models/bingo_model"; -import { Event } from "../models/event_model"; +import { Bingo } from "../models/bingo_model.js"; +import { Event } from "../models/event_model.js"; +import { Prompt } from "../ai/prompt_builder.js"; + +import { GoogleGenAI } from "@google/genai"; +import { z } from "zod"; +import { zodToJsonSchema } from "zod-to-json-schema"; +import "dotenv/config"; + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const aiConstruction = + "You will generate the bingo grid with shortened version of the questions while maintaining the same structure. Each entry should be turned into a max 3 word version of the question that describes what the question is about."; + +const aiShortVersionInstruction = `Generate creative bingo entries based on the provided event context. + +Goal +Create short bingo squares that describe recognizable moments, traits, or behaviors of professionals at the event. + +Length +- 3–8 words per entry. +- Keep entries concise and punchy. + +Key Rule: Be Specific +Entries must describe a concrete behavior, situation, or trait someone could realistically notice at the event. + +Avoid vague descriptions of conversation. + +Avoid phrases like: +- "talking about" +- "discusses" +- "mentions" +- "asks" +- "conversation about" +- "debates" +- "connects on" +- "networking with" + +These are too generic and do not describe an observable bingo moment. + +Instead prefer: + +Observable behaviors +Work-related habits +Professional traits +Real situations that happen during networking +Work artifacts or tools someone references or shows + +Good structural patterns + +Action +Shows photos of a recent project +Sketches an idea on a napkin +Explains how their team works + +Situation +Runs into a former coworker +Recognizes someone from online +Arrives straight from work + +Work artifact +Laptop covered in stickers +Shows a product demo +Pulls up slides on their phone + +Professional trait +Always “too busy lately” +Recently started a new role +Proud of their latest project + +Quote +Has said: "It's been a crazy week" +Has said: "We're hiring right now" + +Context usage +Use the provided event context to inspire industry-relevant references when appropriate. + +Examples +Shows photos of a recent project +Sketches an idea while talking +Proud of their latest work +Runs into someone they know +Recently changed jobs +Laptop covered in company stickers +Shows a demo on their phone +Explains a project they built +Has said: "It's been a long week" + +Avoid +- Generic phrases describing conversation +- Things that always happen at networking events (ex: exchanging LinkedIn) +- Overly abstract statements +- Repeating the same structure across many entries + +Diversity rule +Ensure entries vary in structure, wording, and situation. +No more than two entries may begin with the same first word.`; /** * POST /api/bingo @@ -35,18 +132,24 @@ export async function createBingo(req: Request, res: Response) { } if (grid !== undefined) { - const is2DStringArray = + const isValidGrid = Array.isArray(grid) && grid.every( (row: any) => Array.isArray(row) && - row.every((cell: any) => typeof cell === "string") + row.every( + (cell: any) => + cell && + typeof cell === "object" && + typeof cell.question === "string" && + typeof cell.shortQuestion === "string", + ), ); - if (!is2DStringArray) { + if (!isValidGrid) { return res.status(400).json({ success: false, - msg: "grid must be a 2D array of strings", + msg: "grid must be a 2D array of { question: string, shortQuestion: string }", }); } } @@ -72,9 +175,15 @@ export async function createBingo(req: Request, res: Response) { } } +/** + * @param req.body.eventId - Bingo for a given eventId (string) (required) + */ export async function getBingo(req: Request, res: Response) { try { - const { eventId } = req.params; + const rawEventId = req.params.eventId; + const eventId: string = Array.isArray(rawEventId) + ? rawEventId[0] + : rawEventId; if (!eventId) { return res.status(400).json({ @@ -83,11 +192,7 @@ export async function getBingo(req: Request, res: Response) { }); } - let bingo = await Bingo.findById(eventId); - - if (!bingo && Types.ObjectId.isValid(eventId)) { - bingo = await Bingo.findOne({ _eventId: eventId }); - } + let bingo = await Bingo.findOne({ _eventId: eventId }); if (!bingo) { return res.status(404).json({ @@ -108,7 +213,6 @@ export async function getBingo(req: Request, res: Response) { } } - /** * @param req.body.id - Bingo _id (string) OR Event _id (ObjectId string) (required) * @param req.body.description - New bingo description (string) (optional) @@ -134,18 +238,24 @@ export async function updateBingo(req: Request, res: Response) { } if (grid !== undefined) { - const is2DStringArray = + const isValidGrid = Array.isArray(grid) && grid.every( (row: any) => Array.isArray(row) && - row.every((cell: any) => typeof cell === "string") + row.every( + (cell: any) => + cell && + typeof cell === "object" && + typeof cell.question === "string" && + typeof cell.shortQuestion === "string", + ), ); - if (!is2DStringArray) { + if (!isValidGrid) { return res.status(400).json({ success: false, - msg: "grid must be a 2D array of strings", + msg: "grid must be a 2D array of { question: string, shortQuestion: string }", }); } @@ -159,13 +269,17 @@ export async function updateBingo(req: Request, res: Response) { }); } - let bingo = await Bingo.findByIdAndUpdate(id, { $set: update }, { new: true }); + let bingo = await Bingo.findByIdAndUpdate( + id, + { $set: update }, + { new: true }, + ); if (!bingo && Types.ObjectId.isValid(id)) { bingo = await Bingo.findOneAndUpdate( { _eventId: id }, { $set: update }, - { new: true } + { new: true }, ); } @@ -177,4 +291,261 @@ export async function updateBingo(req: Request, res: Response) { } catch (err: any) { return res.status(500).json({ success: false, error: err.message }); } -} \ No newline at end of file +} + +function makeEmptyGrid(rows: number, cols: number): Record { + const grid: Record = {}; + + for (let r = 1; r <= rows; r++) { + grid[`row${r}`] = Array(cols).fill(""); + } + + return grid; +} + +function buildSchema( + rows: number, + cols: number, +): z.ZodObject>> { + const shape: Record> = {}; + + for (let r = 1; r <= rows; r++) { + shape[`row${r}`] = z + .array( + z + .string() + .describe("A humorous bingo square phrase related to tech culture"), + ) + .length(cols) + .describe(`Row ${r} of the bingo board`); + } + + return z.object(shape); +} + +function buildShapeExample(rows: number, cols: number): string { + const obj: Record = {}; + + for (let r = 1; r <= rows; r++) { + obj[`row${r}`] = Array.from( + { length: cols }, + (_, c) => `Row ${r} Col ${c + 1}`, + ); + } + + return JSON.stringify(obj, null, 2); +} + +/** + * Calls Gemini to generate a bingo grid + */ +async function generateBingoGrid( + n_rows: number, + n_cols: number, + context: string, +): Promise> { + const schema = buildSchema(n_rows, n_cols); + const schemaJson = z.toJSONSchema(schema); + const example = buildShapeExample(n_rows, n_cols); + + const { GoogleGenAI } = await import("@google/genai"); + + const basePrompt_structure = + `Generate a ${n_rows}x${n_cols} bingo board. Return JSON exactly matching this structure: + ${example} + Rules: + - Keys must be row1, row2, row3, etc. + - Each row must contain ${n_cols} strings. + Return ONLY valid JSON. + + You will be provided with additional context to inspire the content of the bingo squares. Use that context to generate relevant bingo square phrases. + `.trim(); + const userContext = `Additional context for bingo content:\n${context}`; + + // const promptPath = new URL( + // "../ai/prompts/bingo_short_questions.txt", + // import.meta.url, + // ); + // const aiInstruction = fs.readFileSync(promptPath, "utf-8"); + + const aiPrompt = new Prompt([ + basePrompt_structure, + userContext, + aiShortVersionInstruction, + ]); + aiPrompt.generatePrompt(); + const prompt = aiPrompt.getPrompt(); + + try { + const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: prompt, + config: { + responseMimeType: "application/json", + responseJsonSchema: schemaJson, + temperature: 0.7, + }, + }); + + if (!response.text) { + throw new Error("Gemini returned empty response"); + } + + const parsed = JSON.parse(response.text); + const validated: Record = schema.parse(parsed); + return validated; + } catch (error) { + console.error("Generation failed:", error); + return makeEmptyGrid(n_rows, n_cols); + } +} + +async function generateBingoGrid_shortVersions( + n_rows: number, + n_cols: number, + original_bingo_questions: string, +): Promise> { + const schema = buildSchema(n_rows, n_cols); + const schemaJson = z.toJSONSchema(schema); + const example = buildShapeExample(n_rows, n_cols); + + const basePrompt_structure = + `Generate a ${n_rows}x${n_cols} bingo board. Return JSON exactly matching this structure: + ${example} + Rules: + - Keys must be row1, row2, row3, etc. + - Each row must contain ${n_cols} strings. + Return ONLY valid JSON. + + You will be provided with additional information about what to put inside the bingo squares. + `.trim(); + const original_bingo_questions_context = `These are the original bingo questions:\n${original_bingo_questions}`; + + // const promptPath = new URL("../ai/prompts/bingo.txt", import.meta.url); + // const aiInstruction = fs.readFileSync(promptPath, "utf-8"); + + const aiPrompt = new Prompt([ + basePrompt_structure, + original_bingo_questions_context, + aiConstruction, + ]); + aiPrompt.generatePrompt(); + const prompt = aiPrompt.getPrompt(); + + try { + const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: prompt, + config: { + responseMimeType: "application/json", + responseJsonSchema: schemaJson, + temperature: 0.7, + }, + }); + + if (!response.text) { + throw new Error("Gemini returned empty response"); + } + + const parsed = JSON.parse(response.text); + const validated: Record = schema.parse(parsed); + return validated; + } catch (error) { + console.error("Short version generation failed:", error); + return makeEmptyGrid(n_rows, n_cols); + } +} + +const apiKey = process.env.GEMINI_API_KEY; + +if (!apiKey) { + throw new Error("GEMINI_API_KEY is not set"); +} +const ai = new GoogleGenAI({ apiKey }); +//takes geminis dumbass output and turns it into a more usable format +function process_ai_result(ai_result: Record) { + const grid: string[][] = []; + + const keys: string[] = Object.keys(ai_result); + for (let i = 0; i < keys.length; i++) { + const val: string[] = ai_result[keys[i]]; + grid.push(val); + } + + return grid; +} + +function combine2DArrays( + arr1: string[][], + arr2: string[][], +): { question: string; shortQuestion: string }[][] { + return arr1.map((row, i) => + row.map((val, j) => ({ + question: val, + shortQuestion: arr2[i]?.[j] || val, + })), + ); +} + +/** + * POST /api/bingo/generate + * + * Generate an AI bingo grid. + * + * @param req.body.context - Context for bingo content (required) + * @param req.body.n_rows - Number of grid rows (1-5) + * @param req.body.n_cols - Number of grid columns (1-5) + * + * @returns 200 with generated bingo grid + * @returns 400 if validation fails + */ +export async function generateBingo(req: Request, res: Response) { + try { + const { context, n_rows, n_cols } = req.body; + + if (!context) { + return res.status(400).json({ + status: false, + msg: "context is required", + }); + } + + if ( + typeof n_rows !== "number" || + typeof n_cols !== "number" || + n_rows <= 0 || + n_cols <= 0 || + n_rows > 5 || + n_cols > 5 + ) { + return res.status(400).json({ + status: false, + msg: "n_rows and n_cols must be numbers where 0 < value <= 5", + }); + } + + const bingo_questions = await generateBingoGrid(n_rows, n_cols, context); + const bingo_short_versions = await generateBingoGrid_shortVersions( + n_rows, + n_cols, + JSON.stringify(bingo_questions), + ); + const bingo_grid_questions: string[][] = process_ai_result(bingo_questions); + const bingo_grid_short_versions: string[][] = + process_ai_result(bingo_short_versions); + const bingo_grid = combine2DArrays( + bingo_grid_questions, + bingo_grid_short_versions, + ); + + return res.status(200).json({ + status: true, + bingo_grid, + }); + } catch (err: any) { + return res.status(500).json({ + status: false, + error: err.message, + }); + } +} diff --git a/shatter-backend/src/controllers/event_controller.ts b/shatter-backend/src/controllers/event_controller.ts index c2c0c91..fd382fa 100644 --- a/shatter-backend/src/controllers/event_controller.ts +++ b/shatter-backend/src/controllers/event_controller.ts @@ -1,15 +1,49 @@ import { Request, Response } from "express"; -import { Event } from "../models/event_model"; -import { pusher } from "../utils/pusher_websocket"; +import { Event } from "../models/event_model.js"; +import { pusher } from "../utils/pusher_websocket.js"; -import "../models/participant_model"; +import "../models/participant_model.js"; -import { generateJoinCode } from "../utils/event_utils"; -import { generateToken } from "../utils/jwt_utils"; -import { Participant } from "../models/participant_model"; -import { User } from "../models/user_model"; +import { generateJoinCode } from "../utils/event_utils.js"; +import { generateToken } from "../utils/jwt_utils.js"; +import { Participant, IParticipant } from "../models/participant_model.js"; +import { User } from "../models/user_model.js"; import { Types } from "mongoose"; +/** + * Create a participant with automatic name suffix on collision. + * If the name already exists in the event, retries with a random #XXX suffix. + */ +async function createParticipantWithRetry( + userId: Types.ObjectId | null, + name: string, + eventId: string, + maxRetries: number = 5 +): Promise<{ participant: IParticipant; finalName: string }> { + let finalName = name; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + const participant = await Participant.create({ + userId, + name: finalName, + eventId, + }); + return { participant, finalName }; + } catch (e: any) { + if (e.code === 11000 && e.keyPattern?.name && e.keyPattern?.eventId) { + const suffix = String(Math.floor(Math.random() * 999) + 1).padStart( + 3, + "0" + ); + finalName = `${name}#${suffix}`; + continue; + } + throw e; + } + } + throw { code: 11000, keyPattern: { name: 1, eventId: 1 } }; +} + /** * POST /api/events/createEvent * Create a new event @@ -136,7 +170,7 @@ export async function getEventByJoinCode(req: Request, res: Response) { export async function joinEventAsUser(req: Request, res: Response) { try { const { name, userId } = req.body; - const { eventId } = req.params; + const eventId = req.params.eventId as string; if (!userId || !name || !eventId) return res.status(400).json({ @@ -159,22 +193,22 @@ export async function joinEventAsUser(req: Request, res: Response) { if (event.participantIds.length >= event.maxParticipant) return res.status(400).json({ success: false, msg: "Event is full" }); - let participant = await Participant.findOne({ + const existingParticipant = await Participant.findOne({ userId, eventId, }); - if (participant) { + if (existingParticipant) { return res .status(409) .json({ success: false, msg: "User already joined" }); } - participant = await Participant.create({ + const { participant, finalName } = await createParticipantWithRetry( userId, name, eventId, - }); + ); const participantId = participant._id as Types.ObjectId; @@ -196,14 +230,14 @@ export async function joinEventAsUser(req: Request, res: Response) { ); console.log("Room socket:", eventId); - console.log("Participant data:", { participantId, name }); + console.log("Participant data:", { participantId, name: finalName }); await pusher.trigger( `event-${eventId}`, // channel (room) "participant-joined", // event name { participantId, - name, + name: finalName, }, ); @@ -213,12 +247,6 @@ export async function joinEventAsUser(req: Request, res: Response) { }); } catch (e: any) { if (e.code === 11000) { - if (e.keyPattern?.name && e.keyPattern?.eventId) { - return res.status(409).json({ - success: false, - msg: "This name is already taken in this event", - }); - } if (e.keyPattern?.email) { return res.status(409).json({ success: false, @@ -244,8 +272,14 @@ export async function joinEventAsUser(req: Request, res: Response) { */ export async function joinEventAsGuest(req: Request, res: Response) { try { - const { name } = req.body; - const { eventId } = req.params; + const { name, email, socialLinks, organization, title } = req.body as { + name?: string; + email?: string; + socialLinks?: { linkedin?: string; github?: string; other?: string }; + organization?: string; + title?: string; + }; + const eventId = req.params.eventId as string; if (!name || !eventId) { return res.status(400).json({ @@ -254,6 +288,31 @@ export async function joinEventAsGuest(req: Request, res: Response) { }); } + // Require at least one contact method or organization + const hasEmail = email && email.trim(); + const hasSocialLink = socialLinks && ( + socialLinks.linkedin?.trim() || + socialLinks.github?.trim() || + socialLinks.other?.trim() + ); + const hasOrganization = organization && organization.trim(); + + if (!hasEmail && !hasSocialLink && !hasOrganization) { + return res.status(400).json({ + success: false, + msg: "At least one contact method (email or social link) or organization is required", + }); + } + + // Validate email format if provided + const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/; + if (hasEmail && !EMAIL_REGEX.test(email.toLowerCase().trim())) { + return res.status(400).json({ + success: false, + msg: "Invalid email format", + }); + } + const event = await Event.findById(eventId); if (!event) { return res.status(404).json({ success: false, msg: "Event not found" }); @@ -267,17 +326,26 @@ export async function joinEventAsGuest(req: Request, res: Response) { const user = await User.create({ name, authProvider: 'guest', + ...(hasEmail && { email: email.toLowerCase().trim() }), + ...(hasSocialLink && { socialLinks }), + ...(hasOrganization && { organization: organization.trim() }), + ...(title && title.trim() && { title: title.trim() }), }); const userId = user._id as Types.ObjectId; const token = generateToken(userId.toString()); - // Create participant linked to the new user - const participant = await Participant.create({ + // Create participant linked to the new user, with automatic #XXX suffix on name collision + const { participant, finalName } = await createParticipantWithRetry( userId, name, eventId, - }); + ); + + // Update guest user's name to match the suffixed participant name + if (finalName !== name) { + await User.updateOne({ _id: userId }, { name: finalName }); + } const participantId = participant._id as Types.ObjectId; @@ -293,14 +361,14 @@ export async function joinEventAsGuest(req: Request, res: Response) { // Emit socket console.log("Room socket:", eventId); - console.log("Participant data:", { participantId, name }); + console.log("Participant data:", { participantId, name: finalName }); await pusher.trigger( `event-${eventId}`, // channel (room) "participant-joined", // event name { participantId, - name, + name: finalName, }, ); @@ -312,12 +380,6 @@ export async function joinEventAsGuest(req: Request, res: Response) { }); } catch (e: any) { if (e.code === 11000) { - if (e.keyPattern?.name && e.keyPattern?.eventId) { - return res.status(409).json({ - success: false, - msg: "This name is already taken in this event", - }); - } if (e.keyPattern?.email) { return res.status(409).json({ success: false, @@ -342,7 +404,7 @@ export async function joinEventAsGuest(req: Request, res: Response) { */ export async function getEventById(req: Request, res: Response) { try { - const { eventId } = req.params; + const eventId = req.params.eventId as string; if (!eventId) { return res @@ -393,7 +455,7 @@ export async function getEventById(req: Request, res: Response) { */ export async function updateEventStatus(req: Request, res: Response) { try { - const { eventId } = req.params; + const eventId = req.params.eventId as string; const { status } = req.body; const validStatuses = ['In Progress', 'Completed']; diff --git a/shatter-backend/src/controllers/participant_connections_controller.ts b/shatter-backend/src/controllers/participant_connections_controller.ts index e66874e..d2ee3e6 100644 --- a/shatter-backend/src/controllers/participant_connections_controller.ts +++ b/shatter-backend/src/controllers/participant_connections_controller.ts @@ -1,12 +1,12 @@ -// controllers/participant_connections_controller.ts +// controllers/participant_connections_controller.js import { Request, Response } from "express"; import { Types } from "mongoose"; -import { check_req_fields } from "../utils/requests_utils"; -import { User } from "../models/user_model"; -import { Participant } from "../models/participant_model"; -import { ParticipantConnection } from "../models/participant_connection_model"; +import { check_req_fields } from "../utils/requests_utils.js"; +import { User } from "../models/user_model.js"; +import { Participant } from "../models/participant_model.js"; +import { ParticipantConnection } from "../models/participant_connection_model.js"; /** * POST /api/participantConnections diff --git a/shatter-backend/src/controllers/user_controller.ts b/shatter-backend/src/controllers/user_controller.ts index 55e745b..2b0796c 100644 --- a/shatter-backend/src/controllers/user_controller.ts +++ b/shatter-backend/src/controllers/user_controller.ts @@ -1,6 +1,7 @@ import { Request, Response } from "express"; -import { User } from "../models/user_model"; -import { hashPassword } from "../utils/password_hash"; +import { User } from "../models/user_model.js"; +import "../models/participant_model.js"; +import { hashPassword } from "../utils/password_hash.js"; const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/; @@ -76,7 +77,14 @@ export const getUserEvents = async (req: Request, res: Response) => { } const user = await User.findById(userId) - .populate("eventHistoryIds", "name description joinCode startDate endDate currentState") + .populate({ + path: "eventHistoryIds", + select: "name description joinCode startDate endDate currentState participantIds", + populate: { + path: "participantIds", + select: "name userId", + }, + }) .select("eventHistoryIds"); if (!user) { @@ -106,13 +114,15 @@ export const updateUser = async (req: Request, res: Response) => { return res.status(403).json({ success: false, error: "You can only update your own profile" }); } - const { name, email, password, bio, profilePhoto, socialLinks } = req.body as { + const { name, email, password, bio, profilePhoto, socialLinks, organization, title } = req.body as { name?: string; email?: string; password?: string; bio?: string; profilePhoto?: string; socialLinks?: { linkedin?: string; github?: string; other?: string }; + organization?: string; + title?: string; }; const updateFields: Record = {}; @@ -153,6 +163,8 @@ export const updateUser = async (req: Request, res: Response) => { if (bio !== undefined) updateFields.bio = bio; if (profilePhoto !== undefined) updateFields.profilePhoto = profilePhoto; if (socialLinks !== undefined) updateFields.socialLinks = socialLinks; + if (organization !== undefined) updateFields.organization = organization; + if (title !== undefined) updateFields.title = title; if (Object.keys(updateFields).length === 0) { return res.status(400).json({ success: false, error: "No fields to update" }); diff --git a/shatter-backend/src/middleware/auth_middleware.ts b/shatter-backend/src/middleware/auth_middleware.ts index 30c79b7..1133f91 100644 --- a/shatter-backend/src/middleware/auth_middleware.ts +++ b/shatter-backend/src/middleware/auth_middleware.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { Request, Response, NextFunction } from 'express'; -import { verifyToken } from '../utils/jwt_utils'; +import { verifyToken } from '../utils/jwt_utils.js'; /** * Extend Express Request type to include user property diff --git a/shatter-backend/src/models/bingo_model.ts b/shatter-backend/src/models/bingo_model.ts index 8ba81b3..9fda83e 100644 --- a/shatter-backend/src/models/bingo_model.ts +++ b/shatter-backend/src/models/bingo_model.ts @@ -1,14 +1,20 @@ -import { Schema, model, Types, Document } from "mongoose"; +import { Schema, model, Types, HydratedDocument } from "mongoose"; +export interface BingoTile { + question: string; + shortQuestion: string; +} -export interface BingoDocument extends Document { +export interface IBingo { _id: string; _eventId: Types.ObjectId; description?: string; - grid?: string[][]; + grid?: BingoTile[][]; } -const bingoSchema = new Schema( +export type BingoDocument = HydratedDocument; + +const bingoSchema = new Schema( { _id: { type: String }, _eventId: { @@ -17,7 +23,12 @@ const bingoSchema = new Schema( required: true, }, description: { type: String }, - grid: { type: [[String]] }, + grid: { + type: [[{ + question: { type: String, required: true }, + shortQuestion: { type: String, required: true }, + }]], + }, }, { versionKey: false, @@ -31,4 +42,4 @@ bingoSchema.pre("save", function (next) { next(); }); -export const Bingo = model("Bingo", bingoSchema); +export const Bingo = model("Bingo", bingoSchema); \ No newline at end of file diff --git a/shatter-backend/src/models/event_model.ts b/shatter-backend/src/models/event_model.ts index 28aec64..380c800 100644 --- a/shatter-backend/src/models/event_model.ts +++ b/shatter-backend/src/models/event_model.ts @@ -1,7 +1,7 @@ import mongoose, { Schema, model, Document, Types } from "mongoose"; -import { User } from "../models/user_model"; +import { User } from "../models/user_model.js"; -import { IParticipant } from "./participant_model"; +import { IParticipant } from "./participant_model.js"; export interface IEvent extends Document { name: string; diff --git a/shatter-backend/src/models/participant_connection_model.ts b/shatter-backend/src/models/participant_connection_model.ts index 5f48106..24e1d17 100644 --- a/shatter-backend/src/models/participant_connection_model.ts +++ b/shatter-backend/src/models/participant_connection_model.ts @@ -1,7 +1,7 @@ import { Schema, model, Types, Document } from "mongoose"; -export interface ParticipantConnection extends Document { - _id: string; +export interface ParticipantConnection { + _id: Types.ObjectId; _eventId: Types.ObjectId; primaryParticipantId: Types.ObjectId; secondaryParticipantId: Types.ObjectId; @@ -10,7 +10,7 @@ export interface ParticipantConnection extends Document { const participantConnectionSchema = new Schema( { - _id: { type: String }, + _id: { type: Schema.Types.ObjectId }, _eventId: { type: Schema.Types.ObjectId, ref: "Event", @@ -35,7 +35,7 @@ const participantConnectionSchema = new Schema( participantConnectionSchema.pre("save", function (next) { if (!this._id) { - this._id = `participantConnection_${Math.random().toString(36).slice(2, 10)}`; + this._id = new Types.ObjectId(); } next(); }); diff --git a/shatter-backend/src/models/user_model.ts b/shatter-backend/src/models/user_model.ts index 78e76c9..3d4bb1c 100644 --- a/shatter-backend/src/models/user_model.ts +++ b/shatter-backend/src/models/user_model.ts @@ -12,6 +12,8 @@ export interface IUser { passwordHash?: string; linkedinId?: string; linkedinUrl?: string; + organization?: string; + title?: string; bio?: string; profilePhoto?: string; socialLinks?: { @@ -65,6 +67,14 @@ const UserSchema = new Schema( unique: true, sparse: true, }, + organization: { + type: String, + trim: true, + }, + title: { + type: String, + trim: true, + }, bio: { type: String, trim: true, diff --git a/shatter-backend/src/routes/auth_routes.ts b/shatter-backend/src/routes/auth_routes.ts index e1487fc..736250c 100644 --- a/shatter-backend/src/routes/auth_routes.ts +++ b/shatter-backend/src/routes/auth_routes.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -import { signup, login, linkedinAuth, linkedinCallback, exchangeAuthCode } from '../controllers/auth_controller'; +import { signup, login, linkedinAuth, linkedinCallback, exchangeAuthCode } from '../controllers/auth_controller.js'; const router = Router(); diff --git a/shatter-backend/src/routes/bingo_routes.ts b/shatter-backend/src/routes/bingo_routes.ts index 7529310..a811f78 100644 --- a/shatter-backend/src/routes/bingo_routes.ts +++ b/shatter-backend/src/routes/bingo_routes.ts @@ -1,16 +1,19 @@ import { Router } from 'express'; -import { createBingo, getBingo, updateBingo} from '../controllers/bingo_controller'; -import { authMiddleware } from '../middleware/auth_middleware'; +import { createBingo, getBingo, updateBingo, generateBingo} from '../controllers/bingo_controller.js'; +import { authMiddleware } from '../middleware/auth_middleware.js'; const router = Router(); router.post('/createBingo', authMiddleware, createBingo); -// POST /api/bingo/getBingo - get bingo details +// GET /api/bingo/getBingo - get bingo details router.get('/getBingo/:eventId', getBingo); -// POST /api/bingo/updateBingo - update bingo details +// PUT /api/bingo/updateBingo - update bingo details router.put("/updateBingo", authMiddleware, updateBingo); +// POST /api/bingo/generateBingo - generate bingo using AI for an event +router.post("/generateBingo", authMiddleware, generateBingo); + export default router; diff --git a/shatter-backend/src/routes/event_routes.ts b/shatter-backend/src/routes/event_routes.ts index 03e2bf9..db982e5 100644 --- a/shatter-backend/src/routes/event_routes.ts +++ b/shatter-backend/src/routes/event_routes.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; -import { createEvent, getEventByJoinCode, getEventById, joinEventAsUser, joinEventAsGuest, getEventsByUserId, updateEventStatus } from '../controllers/event_controller'; -import { authMiddleware } from '../middleware/auth_middleware'; +import { createEvent, getEventByJoinCode, getEventById, joinEventAsUser, joinEventAsGuest, getEventsByUserId, updateEventStatus } from '../controllers/event_controller.js'; +import { authMiddleware } from '../middleware/auth_middleware.js'; const router = Router(); diff --git a/shatter-backend/src/routes/participant_connections_routes.ts b/shatter-backend/src/routes/participant_connections_routes.ts index 8bb6dc8..c05fe2c 100644 --- a/shatter-backend/src/routes/participant_connections_routes.ts +++ b/shatter-backend/src/routes/participant_connections_routes.ts @@ -1,4 +1,4 @@ -// routes/participant_connections_routes.ts +// routes/participant_connections_routes.js import { Router } from "express"; import { @@ -8,8 +8,8 @@ import { getConnectedUsersInfo, getConnectionsByParticipantAndEvent, getConnectionsByUserEmailAndEvent, -} from "../controllers/participant_connections_controller"; -import { authMiddleware } from "../middleware/auth_middleware"; +} from "../controllers/participant_connections_controller.js"; +import { authMiddleware } from "../middleware/auth_middleware.js"; const router = Router(); diff --git a/shatter-backend/src/routes/user_route.ts b/shatter-backend/src/routes/user_route.ts index 4d7688a..3989ce2 100644 --- a/shatter-backend/src/routes/user_route.ts +++ b/shatter-backend/src/routes/user_route.ts @@ -1,7 +1,7 @@ import { Router, Request, Response } from 'express'; -import { getUsers, createUser, getUserById, getUserEvents, updateUser } from '../controllers/user_controller'; -import { authMiddleware } from '../middleware/auth_middleware'; -import { User } from '../models/user_model'; +import { getUsers, createUser, getUserById, getUserEvents, updateUser } from '../controllers/user_controller.js'; +import { authMiddleware } from '../middleware/auth_middleware.js'; +import { User } from '../models/user_model.js'; const router = Router(); diff --git a/shatter-backend/src/server.ts b/shatter-backend/src/server.ts index e209977..2b207fa 100644 --- a/shatter-backend/src/server.ts +++ b/shatter-backend/src/server.ts @@ -1,6 +1,6 @@ import "dotenv/config"; -import mongoose from "mongoose"; -import app from "./app"; +import { connectDB } from "./utils/db.js"; +import app from "./app.js"; const PORT = process.env.PORT ? Number(process.env.PORT) : 4000; const MONGODB_URI = process.env.MONGO_URI; @@ -25,7 +25,7 @@ async function start() { if (!MONGODB_URI) { throw new Error("MONGO_URI is not set"); } - await mongoose.connect(MONGODB_URI); + await connectDB(MONGODB_URI); console.log("Successfully connected to MongoDB"); app.listen(PORT, () => { diff --git a/shatter-backend/src/utils/db.ts b/shatter-backend/src/utils/db.ts new file mode 100644 index 0000000..2e5f65e --- /dev/null +++ b/shatter-backend/src/utils/db.ts @@ -0,0 +1,84 @@ +import mongoose from "mongoose"; +import { Request, Response, NextFunction } from "express"; + +let connectionPromise: Promise | null = null; +let listenersRegistered = false; + +function registerListeners(): void { + if (listenersRegistered) return; + listenersRegistered = true; + + mongoose.connection.on("connected", () => console.log("MongoDB: connected")); + mongoose.connection.on("disconnected", () => { + console.log("MongoDB: disconnected"); + connectionPromise = null; + }); + mongoose.connection.on("reconnected", () => console.log("MongoDB: reconnected")); + mongoose.connection.on("error", (err) => { + console.error("MongoDB: error:", err); + connectionPromise = null; + }); +} + +export async function connectDB(uri: string): Promise { + registerListeners(); + + // If connected, verify the connection is actually alive (not stale from serverless freeze) + if (mongoose.connection.readyState === 1) { + try { + await mongoose.connection.db!.admin().ping(); + return; // genuinely alive + } catch { + console.log("MongoDB connection stale, reconnecting..."); + await mongoose.disconnect(); + connectionPromise = null; + } + } + + // If in a transitional state (connecting/disconnecting), reset + if (mongoose.connection.readyState !== 0) { + await mongoose.disconnect(); + connectionPromise = null; + } + + // Reuse in-flight connection attempt + if (connectionPromise) { + return connectionPromise; + } + + connectionPromise = (async () => { + try { + await mongoose.connect(uri, { + bufferCommands: false, + maxPoolSize: 5, + serverSelectionTimeoutMS: 5000, + socketTimeoutMS: 30000, + heartbeatFrequencyMS: 10000, + }); + console.log("MongoDB connected"); + } catch (error) { + console.error("MongoDB connection failed:", error); + connectionPromise = null; + throw error; + } + })(); + + return connectionPromise; +} + +export function ensureConnection(req: Request, res: Response, next: NextFunction): void { + const uri = process.env.MONGO_URI; + if (!uri) { + res.status(500).json({ error: "MONGO_URI is not configured" }); + return; + } + + connectDB(uri) + .then(() => next()) + .catch((error: any) => { + res.status(500).json({ + error: "Database connection failed", + message: error.message, + }); + }); +} diff --git a/shatter-backend/src/utils/test_jwt.ts b/shatter-backend/src/utils/test_jwt.ts index edd99e8..7cc54db 100644 --- a/shatter-backend/src/utils/test_jwt.ts +++ b/shatter-backend/src/utils/test_jwt.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; // Load environment variables dotenv.config(); -import { generateToken, verifyToken } from './jwt_utils'; +import { generateToken, verifyToken } from './jwt_utils.js'; function testJWT() { diff --git a/shatter-backend/src/utils/test_password.ts b/shatter-backend/src/utils/test_password.ts index e8ff743..0cbe1fb 100644 --- a/shatter-backend/src/utils/test_password.ts +++ b/shatter-backend/src/utils/test_password.ts @@ -1,4 +1,4 @@ -import { hashPassword, comparePassword } from './password_hash'; +import { hashPassword, comparePassword } from './password_hash.js'; async function testPasswordHashing() { console.log('🧪 Testing Password Hashing...\n'); diff --git a/shatter-backend/tsconfig.json b/shatter-backend/tsconfig.json index c997406..0dee230 100644 --- a/shatter-backend/tsconfig.json +++ b/shatter-backend/tsconfig.json @@ -1,14 +1,15 @@ { "compilerOptions": { - "target": "ES2021", - "module": "node16", - "moduleResolution": "node16", + "target": "ES2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", "esModuleInterop": true, "strict": true, "sourceMap": true, "outDir": "./dist", "rootDir": "./", - "lib": ["ES2021"], + "lib": ["ES2020", "DOM"], + "skipLibCheck": true, "typeRoots": ["./src/types", "./node_modules/@types"] }, "include": ["src/**/*", "api/**/*"], diff --git a/shatter-mobile/.gitignore b/shatter-mobile/.gitignore index b04db4d..c4b4c18 100644 --- a/shatter-mobile/.gitignore +++ b/shatter-mobile/.gitignore @@ -42,4 +42,5 @@ app-example /ios /android -.env \ No newline at end of file +.env +.vercel diff --git a/shatter-mobile/.vercel/README.txt b/shatter-mobile/.vercel/README.txt new file mode 100644 index 0000000..525d8ce --- /dev/null +++ b/shatter-mobile/.vercel/README.txt @@ -0,0 +1,11 @@ +> Why do I have a folder named ".vercel" in my project? +The ".vercel" folder is created when you link a directory to a Vercel project. + +> What does the "project.json" file contain? +The "project.json" file contains: +- The ID of the Vercel project that you linked ("projectId") +- The ID of the user or team your Vercel project is owned by ("orgId") + +> Should I commit the ".vercel" folder? +No, you should not share the ".vercel" folder with anyone. +Upon creation, it will be automatically added to your ".gitignore" file. diff --git a/shatter-mobile/.vercel/project.json b/shatter-mobile/.vercel/project.json new file mode 100644 index 0000000..b1a5638 --- /dev/null +++ b/shatter-mobile/.vercel/project.json @@ -0,0 +1 @@ +{"projectId":"prj_C1w4tnphhWmiAmCXd0fpds6ezIfy","orgId":"team_3uMc5YxTEePloUrIUwlmMIl7","projectName":"shatter-mobile"} \ No newline at end of file diff --git a/shatter-mobile/app/(tabs)/EventsPage.tsx b/shatter-mobile/app/(tabs)/EventsPage.tsx index 9ee1a68..8634098 100644 --- a/shatter-mobile/app/(tabs)/EventsPage.tsx +++ b/shatter-mobile/app/(tabs)/EventsPage.tsx @@ -11,8 +11,8 @@ import { View, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; -import AnimatedTab from "../../src/components/AnimatedTab"; import EventCard from "../../src/components/events/EventCard"; +import AnimatedTab from "../../src/components/general/AnimatedTab"; import EventIB from "../../src/interfaces/Event"; import { getUserEvents } from "../../src/services/event.service"; import { EventPageStyling as styles } from "../../src/styling/EventPage.styles"; diff --git a/shatter-mobile/app/(tabs)/JoinEventPage.tsx b/shatter-mobile/app/(tabs)/JoinEventPage.tsx index fe78d8d..11eb4e4 100644 --- a/shatter-mobile/app/(tabs)/JoinEventPage.tsx +++ b/shatter-mobile/app/(tabs)/JoinEventPage.tsx @@ -1,17 +1,19 @@ import { useJoinEvent } from "@/src/components/new-events/JoinEvent"; +import { colors } from "@/src/styling/constants"; +import { Ionicons } from "@expo/vector-icons"; import { router } from "expo-router"; import { useState } from "react"; import { - ActivityIndicator, - Button, - ImageBackground, - Text, - TextInput, - View, + ActivityIndicator, + ImageBackground, + Text, + TextInput, + TouchableOpacity, + View, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; -import AnimatedTab from "../../src/components/AnimatedTab"; import { useAuth } from "../../src/components/context/AuthContext"; +import AnimatedTab from "../../src/components/general/AnimatedTab"; import QRScannerBox from "../../src/components/new-events/QRScannerBox"; import { JoinEventStyling as styles } from "../../src/styling/JoinEventPage.styles"; @@ -66,35 +68,65 @@ export default function JoinEventPage() { {!loading && ( <> - {!showScanner && ( - diff --git a/shatter-web/src/components/icons/BarsIcon.tsx b/shatter-web/src/components/icons/BarsIcon.tsx new file mode 100644 index 0000000..12787cc --- /dev/null +++ b/shatter-web/src/components/icons/BarsIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function BarsIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/CalendarIcon.tsx b/shatter-web/src/components/icons/CalendarIcon.tsx new file mode 100644 index 0000000..ed1b0f9 --- /dev/null +++ b/shatter-web/src/components/icons/CalendarIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function CalendarIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/ChevronDownIcon.tsx b/shatter-web/src/components/icons/ChevronDownIcon.tsx new file mode 100644 index 0000000..53ef640 --- /dev/null +++ b/shatter-web/src/components/icons/ChevronDownIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function ChevronDownIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/ClipboardCopyIcon.tsx b/shatter-web/src/components/icons/ClipboardCopyIcon.tsx new file mode 100644 index 0000000..9f625c0 --- /dev/null +++ b/shatter-web/src/components/icons/ClipboardCopyIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function ClipboardCopyIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/ClipboardIcon.tsx b/shatter-web/src/components/icons/ClipboardIcon.tsx new file mode 100644 index 0000000..cc94921 --- /dev/null +++ b/shatter-web/src/components/icons/ClipboardIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function ClipboardIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/ClockIcon.tsx b/shatter-web/src/components/icons/ClockIcon.tsx new file mode 100644 index 0000000..da14c3b --- /dev/null +++ b/shatter-web/src/components/icons/ClockIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function ClockIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/GoogleIcon.tsx b/shatter-web/src/components/icons/GoogleIcon.tsx new file mode 100644 index 0000000..1927800 --- /dev/null +++ b/shatter-web/src/components/icons/GoogleIcon.tsx @@ -0,0 +1,24 @@ +import type { IconProps } from "./types"; + +export function GoogleIcon({ className = "w-5 h-5", ...props }: IconProps) { + return ( + + + + + + + ); +} diff --git a/shatter-web/src/components/icons/InformationCircleIcon.tsx b/shatter-web/src/components/icons/InformationCircleIcon.tsx new file mode 100644 index 0000000..4d17882 --- /dev/null +++ b/shatter-web/src/components/icons/InformationCircleIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function InformationCircleIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/KeyIcon.tsx b/shatter-web/src/components/icons/KeyIcon.tsx new file mode 100644 index 0000000..da05a75 --- /dev/null +++ b/shatter-web/src/components/icons/KeyIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function KeyIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/PlusIcon.tsx b/shatter-web/src/components/icons/PlusIcon.tsx new file mode 100644 index 0000000..0ebfdeb --- /dev/null +++ b/shatter-web/src/components/icons/PlusIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function PlusIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/SearchIcon.tsx b/shatter-web/src/components/icons/SearchIcon.tsx new file mode 100644 index 0000000..c1a2cd6 --- /dev/null +++ b/shatter-web/src/components/icons/SearchIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function SearchIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/UsersIcon.tsx b/shatter-web/src/components/icons/UsersIcon.tsx new file mode 100644 index 0000000..f506fa5 --- /dev/null +++ b/shatter-web/src/components/icons/UsersIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function UsersIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/XIcon.tsx b/shatter-web/src/components/icons/XIcon.tsx new file mode 100644 index 0000000..05fe53b --- /dev/null +++ b/shatter-web/src/components/icons/XIcon.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function XIcon({ className = "w-4 h-4", ...props }: IconProps) { + return ( + + + + ); +} diff --git a/shatter-web/src/components/icons/index.ts b/shatter-web/src/components/icons/index.ts new file mode 100644 index 0000000..ffc696b --- /dev/null +++ b/shatter-web/src/components/icons/index.ts @@ -0,0 +1,13 @@ +export { BarsIcon } from "./BarsIcon"; +export { CalendarIcon } from "./CalendarIcon"; +export { ChevronDownIcon } from "./ChevronDownIcon"; +export { ClipboardCopyIcon } from "./ClipboardCopyIcon"; +export { ClipboardIcon } from "./ClipboardIcon"; +export { ClockIcon } from "./ClockIcon"; +export { GoogleIcon } from "./GoogleIcon"; +export { InformationCircleIcon } from "./InformationCircleIcon"; +export { KeyIcon } from "./KeyIcon"; +export { PlusIcon } from "./PlusIcon"; +export { SearchIcon } from "./SearchIcon"; +export { UsersIcon } from "./UsersIcon"; +export { XIcon } from "./XIcon"; diff --git a/shatter-web/src/components/icons/types.ts b/shatter-web/src/components/icons/types.ts new file mode 100644 index 0000000..a580585 --- /dev/null +++ b/shatter-web/src/components/icons/types.ts @@ -0,0 +1,6 @@ +import type { SVGProps } from "react"; + +export interface IconProps extends SVGProps { + className?: string; + size?: number; +} diff --git a/shatter-web/src/hooks/useEventData.ts b/shatter-web/src/hooks/useEventData.ts index 79d696e..c583998 100644 --- a/shatter-web/src/hooks/useEventData.ts +++ b/shatter-web/src/hooks/useEventData.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import type { Participant } from "../types/participant"; interface EventDetails { @@ -11,6 +11,7 @@ interface EventDetails { maxParticipant: number; currentState: string; participantIds: Participant[]; + createdBy?: string; } interface EventResponse { @@ -25,17 +26,16 @@ export function useEventData(joinCode: string | undefined) { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - useEffect(() => { + const fetchEvent = useCallback((signal?: AbortSignal) => { if (!joinCode) { setLoading(false); return; } - setLoading(true); setError(""); - fetch(`${import.meta.env.VITE_API_URL}/events/event/${joinCode}`, { cache: "no-store", + signal, }) .then((res) => { if (!res.ok) { @@ -45,9 +45,10 @@ export function useEventData(joinCode: string | undefined) { }) .then((data: EventResponse) => { if (data.success && data.event) { - setEventId(data.event._id); - setEventDetails(data.event); - setParticipants(data.event.participantIds); + const ev = data.event; + setEventId(ev._id); + setEventDetails(ev); + setParticipants(ev.participantIds || []); } else { setEventId(null); setEventDetails(null); @@ -55,6 +56,7 @@ export function useEventData(joinCode: string | undefined) { } }) .catch((err) => { + if (err?.name === "AbortError") return; console.error("Error fetching event:", err); setEventId(null); setEventDetails(null); @@ -63,5 +65,14 @@ export function useEventData(joinCode: string | undefined) { .finally(() => setLoading(false)); }, [joinCode]); - return { eventId, eventDetails, participants, loading, error }; + useEffect(() => { + if (!joinCode) return; + const controller = new AbortController(); + fetchEvent(controller.signal); + return () => controller.abort(); + }, [joinCode, fetchEvent]); + + const refetch = () => fetchEvent(); + + return { eventId, eventDetails, participants, loading, error, refetch }; } diff --git a/shatter-web/src/pages/CreateEventPage.tsx b/shatter-web/src/pages/CreateEventPage.tsx index effc0b2..6cce9c6 100644 --- a/shatter-web/src/pages/CreateEventPage.tsx +++ b/shatter-web/src/pages/CreateEventPage.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; -import { useEffect } from "react"; import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; +import BingoTable from "../components/BingoTable"; import { CreateEvent } from "../service/CreateEvent"; import { createBingoGame } from "../service/BingoGame"; // ✅ NEW import { useNavigate } from "react-router-dom"; @@ -16,7 +16,10 @@ function CreateEventPage() { ); // ✅ NEW: Name Bingo selection + const createEmptyGrid = (size: number) => Array.from({ length: size }, () => Array(size).fill("")); const [nameBingoSelected, setNameBingoSelected] = useState(false); + const [bingoGrid, setBingoGrid] = useState(createEmptyGrid(3)); + const [bingoDescription, setBingoDescription] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -28,6 +31,26 @@ function CreateEventPage() { setLoading(true); setError(null); + // ✅ Validate main form + if (!name.trim() || !description.trim() || !startDate || !maxParticipant || maxParticipant <= 0) { + throw new Error("Please fill in all required fields."); + } + + // ✅ Validate bingo (if selected) + if (nameBingoSelected) { + const hasEmptyCells = bingoGrid.some(row => + row.some(cell => !cell.trim()) + ); + + if (hasEmptyCells) { + throw new Error("Please fill in all bingo grid cells."); + } + + if (!bingoDescription.trim()) { + throw new Error("Please add a bingo description."); + } + } + // 1️⃣ Create event const { eventId, joinCode } = await CreateEvent({ name, @@ -37,19 +60,36 @@ function CreateEventPage() { maxParticipants: maxParticipant ?? 0, }); - // 2️⃣ If Name Bingo selected, create bingo game + // 2️⃣ Create bingo (ONLY ONE CALL) if (nameBingoSelected) { const token = localStorage.getItem("token"); - if (!token) { - throw new Error("Authentication required to create bingo game."); - } + if (!token) throw new Error("Authentication required."); - await createBingoGame(eventId, token); + const response = await fetch(`${import.meta.env.VITE_API_URL}/bingo/createBingo`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + _eventId: eventId, + description: bingoDescription, + grid: bingoGrid, + }), + }); + + if (!response.ok) { + const errData = await response.json().catch(() => ({})); + throw new Error(errData.message || "Failed to create bingo game."); + } } - // 3️⃣ Navigate to the newly created event page + // 3️⃣ Navigate + setLoading(false); navigate(`/events/${joinCode}`); + } catch (err: any) { + console.error("Create event error:", err); setError(err.message || "Failed to create event. Please try again."); setLoading(false); } @@ -74,6 +114,11 @@ function CreateEventPage() { maxParticipant !== undefined && maxParticipant > 0; + const token = localStorage.getItem("token"); + if (!token) { + navigate("/login"); + return; + } return (
setMaxParticipant(Number(e.target.value))} + onChange={(e) => { + const val = e.target.value; + setMaxParticipant(val === "" ? undefined : Number(val)); + }} min="1" disabled={loading} /> @@ -239,6 +287,31 @@ function CreateEventPage() {
+ {/*Icebreaker Properties*/} + {nameBingoSelected && ( +
+
+ + setBingoDescription(e.target.value)} + className="w-full p-3 rounded bg-white border text-black" + /> +
+ + { + const newGrid = bingoGrid.map(r => [...r]); // ✅ deep copy + newGrid[row][col] = value; + setBingoGrid(newGrid); + }} + /> +
+ )} + {/* Action Buttons */}
@@ -447,9 +435,7 @@ function DashboardPage() {
- - - + {formatDate(event.startDate)}
@@ -571,7 +557,7 @@ function DashboardPage() { onChange={(e) => setEditForm({ ...editForm, currentState: e.target.value })} className="w-full p-3 rounded-lg bg-white/5 border border-white/20 text-white focus:outline-none focus:border-[#4DC4FF] transition-colors font-body" > - + @@ -606,9 +592,7 @@ function DashboardPage() {
- - - + Start Date

{formatDate(selectedEvent.startDate)}

@@ -617,9 +601,7 @@ function DashboardPage() {
- - - + End Date

{formatDate(selectedEvent.endDate)}

@@ -628,9 +610,7 @@ function DashboardPage() {
- - - + Participants

@@ -640,9 +620,7 @@ function DashboardPage() {

- - - + Join Code

{selectedEvent.joinCode}

@@ -656,15 +634,17 @@ function DashboardPage() { {!selectedIcebreaker && (
-
@@ -731,40 +703,19 @@ function DashboardPage() {
- - -
- {bingoGrid.map((row, rowIndex) => - row.map((cell, colIndex) => ( -
- handleBingoGridChange(rowIndex, colIndex, e.target.value)} - placeholder={`${rowIndex + 1}-${colIndex + 1}`} - className="w-full h-24 p-2 rounded-lg bg-white/5 border border-white/20 text-white text-xs placeholder-white/30 focus:outline-none focus:border-[#4DC4FF] focus:ring-2 focus:ring-[#4DC4FF]/20 transition-all font-body resize-none" - style={{ - fontSize: '0.75rem', - lineHeight: '1.2', - }} - /> -
- {rowIndex * 5 + colIndex + 1} -
-
- )) - )} -
+ { + const newGrid = bingoGrid.map(r => [...r]); + newGrid[row][col] = value; + setBingoGrid(newGrid); + }} + />
- - - +

Tips for creating good bingo questions:

    @@ -777,6 +728,26 @@ function DashboardPage() {
+ {bingoSaveMessage && ( +
+ {bingoSaveMessage.text} + +
+ )} +
+ +
+ )}
+ {/* Live Activity Spotlight */} +
+ +
+ {/* Main Content Grid */}
{/* Left Column - Event Details */} @@ -188,20 +316,7 @@ export default function EventPage() { {/* Start Date */}
- {/* Calendar Icon */} - - - +

Start

@@ -217,20 +332,7 @@ export default function EventPage() { {/* End Date */}
- {/* Calendar Icon */} - - - +

End

@@ -328,19 +430,7 @@ export default function EventPage() { className="w-full px-4 py-2.5 rounded-full font-semibold text-white hover:opacity-90 transition-opacity shadow-lg font-body flex items-center justify-center gap-2" style={{ backgroundColor: "#4DC4FF" }} > - - - + Copy Join Code
@@ -364,16 +454,23 @@ export default function EventPage() { Bingo game associated with this event.

-
+
{bingoGame.grid.flat().map((cell, index) => (
- {cell} + {cell}
))}
diff --git a/shatter-web/src/pages/LoginPage.tsx b/shatter-web/src/pages/LoginPage.tsx index fefc6ec..7caf7ef 100644 --- a/shatter-web/src/pages/LoginPage.tsx +++ b/shatter-web/src/pages/LoginPage.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { GoogleIcon } from '../components/icons'; export default function LoginPage() { const navigate = useNavigate(); @@ -280,12 +281,7 @@ export default function LoginPage() { type="button" className="w-full py-3 rounded-lg border border-white/20 bg-white/5 text-white hover:bg-white/10 transition-colors font-body flex items-center justify-center gap-3" > - - - - - - + Continue with Google
diff --git a/shatter-web/src/service/BingoGame.ts b/shatter-web/src/service/BingoGame.ts index 63226c3..3191de8 100644 --- a/shatter-web/src/service/BingoGame.ts +++ b/shatter-web/src/service/BingoGame.ts @@ -1,7 +1,6 @@ // services/BingoGame.ts -const CREATE_BINGO_URL = - "https://techstart-shatter-backend.vercel.app/api/bingo/createBingo"; +const BASE_URL = import.meta.env.VITE_API_URL ?? "https://techstart-shatter-backend.vercel.app/api"; export interface BingoGame { _id: string; @@ -10,11 +9,18 @@ export interface BingoGame { grid: string[][]; } +export async function getBingo(eventId: string): Promise { + const res = await fetch(`${BASE_URL}/bingo/getBingo/${eventId}`); + const data = await res.json(); + if (!res.ok || !data?.bingo) return null; + return data.bingo; +} + export async function createBingoGame( eventId: string, token: string ): Promise { - const res = await fetch(CREATE_BINGO_URL, { + const res = await fetch(`${BASE_URL}/bingo/createBingo`, { method: "POST", headers: { "Content-Type": "application/json", @@ -24,11 +30,9 @@ export async function createBingoGame( _eventId: eventId, // ✅ THIS IS REQUIRED description: "Name Bingo", grid: [ - ["A1", "B1", "C1", "D1", "E1"], - ["A2", "B2", "C2", "D2", "E2"], - ["A3", "B3", "C3", "D3", "E3"], - ["A4", "B4", "C4", "D4", "E4"], - ["A5", "B5", "C5", "D5", "E5"], + ["A1", "B1", "C1"], + ["A2", "B2", "C2"], + ["A3", "B3", "C3"] ], }), }); diff --git a/shatter-web/src/service/CreateEvent.ts b/shatter-web/src/service/CreateEvent.ts index 0725057..18726cb 100644 --- a/shatter-web/src/service/CreateEvent.ts +++ b/shatter-web/src/service/CreateEvent.ts @@ -25,7 +25,8 @@ export async function CreateEvent(eventData: { startDate, endDate, maxParticipant: eventData.maxParticipants, - currentState: 'upcoming', + currentState: 'Upcoming', + gameType: 'Name Bingo', }; const apiUrl = `${import.meta.env.VITE_API_URL}/events/createEvent`;