diff --git a/README.md b/README.md index e0396b3..a5cc3f3 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,72 @@ -# WEC Project Expo - 2025 +# WEC Project Expo - 2026 + # Orbis Orbis is a sophisticated event management platform designed to simplify and elevate the experience of organizing, hosting, and participating in events at NITK. -## Overview +## Table of contents + +- [The Problem](#the-problem) +- [Our solution](#our-solution) +- [Features](#features) + - [User profiles and authentication](#user-profiles-and-authentication) + - [Event creation and management](#event-creation-and-management) + - [Community module](#community-module) + - [Project showcase](#project-showcase) + - [Form creation](#form-creation) +- [Setup and Development Guidelines](#setup-and-development-guidelines) +- [Deployment](#deployment) +- [Team Members](#team-members) + +## The Problem + +- Currently, information regarding events has to be spammed on WhatsApp groups, leading to inefficiencies in tracking. Forms have to be created by organisers using google forms or similar websites. Maintaining oversight of events, deadlines, and numerous registration forms is challenging. Also, there is no dedicated project showcase website. Projects are primarily hosted on individual club websites. There exists no community module, restricting interactions with campus peers on alternative platforms. + +## Our Solution + +- Orbis provides a centralized platform that streamlines event discovery, registration, and management. Instead of relying on scattered WhatsApp messages and multiple external links, all event related information, including details, deadlines, and application forms is organized in one place. + +- Enables organizers to create and manage events, handle applications, track participants and maintain structured workflows. For participants, it simplifies discovering events, applying individually or in teams, and monitoring application status in real time. + +- Beyond events, Orbis introduces a community-driven ecosystem, allowing users to interact through posts, comments, friend connections, and private messaging fostering stronger campus engagement within a single platform. + +- It also serves as a dedicated project showcase, where clubs can present their work, making projects more accessible across the institute. + +- Additionally, the platform allows form creation, enabling organizers to create and manage custom forms with ease, eliminating dependency on external tools. + +- Overall, Orbis transforms fragmented workflows into a unified, scalable, and user-friendly ecosystem. + +## Features -This platform features: -- Event creation and management -- User profiles and authentication -- Team formation and management -- Project submissions -- Application tracking +### User profiles and authentication + +- Uses Auth0. +- Users can enter required details, create their profiles, login and navigate the app. + +### Event creation and management + +- Organisers can create event, accept/reject applications and update participants' attendance. +- Users can apply for an event individually or in a team and track the status of application. + +### Community module + +- Users can create public posts, react and comment on an existing post, discover people and add friends. +- Messages allow users to chat with their friends privately. + +### Project showcase + +- Has a list of clubs across the campus. +- Conveners can add projects to their respective clubs along with a short description, contributors, and the links to GitHub repo and live demo. +- Users can view projects and participate in discussions. + +### Form creation + +- Organisers can create forms with custom questions and retrieve responses. The question can be of the type: short-answer/paragraph/MCQ/rating/drop-down. +- Users can access all the active forms, fill and submit. ## Database Schema - + +[View Prisma Schema](./backend/prisma/schema.prisma) ## Tech Stack @@ -23,21 +76,24 @@ This platform features: - **ORM**: Prisma - **Authentication**: Auth0 -## Setup & Development Guidelines +## Setup and Development Guidelines ### Prerequisites + - [Node.js](https://nodejs.org/) (v16 or higher) - [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/) ### Project Setup 1. **Clone the Repository** + ```bash git clone https://github.com/yourusername/orbis.git cd orbis ``` 2. **Backend Setup** + ```bash cd backend npm install @@ -45,37 +101,42 @@ This platform features: 3. **Backend Environment Configuration** Create a `.env` file in the backend directory with the following variables: + ``` DATABASE_URL="your-supabase-postgres-connection-string" PORT=4000 NODE_ENV=development - + # Use your frontend deployment URL in production CORS_ORIGIN="https://your-frontend-domain.com" # For local development # CORS_ORIGIN="http://localhost:3000" - + AUTH0_ISSUER_BASE_URL="your-auth0-issuer-url" AUTH0_AUDIENCE="your-auth0-audience" AUTH0_CLIENT_SECRET="your-auth0-client-secret" ``` 4. **Prisma Setup** + ```bash # Generate Prisma client npx prisma generate - + # Push schema changes to database (development only) npx prisma db push ``` 5. **Start the Backend Server** + ```bash npm run dev ``` + The backend will run on http://localhost:4000 in development 6. **Frontend Setup** (in a new terminal) + ```bash cd ../frontend npm install @@ -83,17 +144,18 @@ This platform features: 7. **Frontend Environment Configuration** Create a `.env` file in the frontend directory with the following variables: + ``` # Use your backend deployment URL in production VITE_API_URL="https://your-backend-domain.com" - # For local development + # For local development # VITE_API_URL="http://localhost:4000" - + # Supabase VITE_SUPABASE_URL="your-supabase-url" VITE_SUPABASE_ANON_KEY="your-supabase-anon-key" VITE_SUPABASE_SERVICE_ROLE_KEY="your-supabase-service-role-key" - + # Auth0 VITE_AUTH0_DOMAIN="your-auth0-domain" VITE_AUTH0_CLIENT_ID="your-auth0-client-id" @@ -147,6 +209,7 @@ Prisma Studio will be available at http://localhost:5555 when running the `npx p ### Prisma Schema The database schema is defined in `prisma/schema.prisma`. This file defines: + - Data models - Relationships between models - Default values @@ -167,10 +230,12 @@ The backend provides RESTful API endpoints for various features: ## Code Practices ### Comments + - Use clear and concise comments to explain complex code blocks - Use JSDoc style comments for functions and components ### Code Style + - Follow the Airbnb JavaScript Style Guide - Use 2 spaces for indentation - Prefer `const` and `let` over `var` @@ -179,12 +244,14 @@ The backend provides RESTful API endpoints for various features: - Place opening braces on the same line as the statement ### Variable Style + - Use camelCase for variable and function names - Use PascalCase for React components and class names - Use UPPER_SNAKE_CASE for constants - Choose meaningful and descriptive names ### Commit Style + - Write clear and concise commit messages - Use present tense and imperative mood - Capitalize the first letter @@ -193,7 +260,19 @@ The backend provides RESTful API endpoints for various features: - Use prefixes like `fix:`, `feat:`, `frontend:`, `backend:`, `misc:` ### Development Style + - Use feature branches for new features and bug fixes - Keep the `main` branch in a deployable state - Regularly pull changes from `main` - Perform code reviews and seek feedback + +## Deployment + +- **Frontend**: +- **Backend**: + +## Team Members + +- Aman Nagpal +- Chinmayee M +- Ritvik Gampa diff --git a/backend/package-lock.json b/backend/package-lock.json index bb0e6b3..ff95b09 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/client": "^6.0.1", "bcrypt": "^5.1.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -18,11 +17,16 @@ "express-oauth2-jwt-bearer": "^1.6.0", "express-session": "^1.18.1", "pg": "^8.10.0", - "prisma": "^6.0.1", + "psql": "^0.0.1", + "socket.io": "^4.8.3", "zod": "^3.22.4" }, "devDependencies": { - "nodemon": "^2.0.22" + "@prisma/client": "^5.22.0", + "i": "^0.3.7", + "nodemon": "^2.0.22", + "npm": "^11.12.1", + "prisma": "^5.22.0" } }, "node_modules/@mapbox/node-pre-gyp": { @@ -58,13 +62,14 @@ } }, "node_modules/@prisma/client": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.0.1.tgz", - "integrity": "sha512-60w7kL6bUxz7M6Gs/V+OWMhwy94FshpngVmOY05TmGD0Lhk+Ac0ZgtjlL6Wll9TD4G03t4Sq1wZekNVy+Xdlbg==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "engines": { - "node": ">=18.18" + "node": ">=16.13" }, "peerDependencies": { "prisma": "*" @@ -76,48 +81,74 @@ } }, "node_modules/@prisma/debug": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.0.1.tgz", - "integrity": "sha512-jQylgSOf7ibTVxqBacnAlVGvek6fQxJIYCQOeX2KexsfypNzXjJQSS2o5s+Mjj2Np93iSOQUaw6TvPj8syhG4w==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.0.1.tgz", - "integrity": "sha512-4hxzI+YQIR2uuDyVsDooFZGu5AtixbvM2psp+iayDZ4hRrAHo/YwgA17N23UWq7G6gRu18NvuNMb48qjP3DPQw==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.0.1", - "@prisma/engines-version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", - "@prisma/fetch-engine": "6.0.1", - "@prisma/get-platform": "6.0.1" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e.tgz", - "integrity": "sha512-JmIds0Q2/vsOmnuTJYxY4LE+sajqjYKhLtdOT6y4imojqv5d/aeVEfbBGC74t8Be1uSp0OP8lxIj2OqoKbLsfQ==", + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.0.1.tgz", - "integrity": "sha512-T36bWFVGeGYYSyYOj9d+O9G3sBC+pAyMC+jc45iSL63/Haq1GrYjQPgPMxrEj9m739taXrupoysRedQ+VyvM/Q==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.0.1", - "@prisma/engines-version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", - "@prisma/get-platform": "6.0.1" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.0.1.tgz", - "integrity": "sha512-zspC9vlxAqx4E6epMPMLLBMED2VD8axDe8sPnquZ8GOsn6tiacWK0oxrGK4UAHYzYUVuMVUApJbdXB2dFpLhvg==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.0.1" + "@prisma/debug": "5.22.0" + } + }, + "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", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", + "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "dependencies": { + "undici-types": "~7.18.0" } }, "node_modules/abbrev": { @@ -217,18 +248,54 @@ "node": ">=10" } }, + "node_modules/argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha512-LjmC2dNpdn2L4UzyoaIr11ELYoLn37ZFy9zObrQFHsSuOepeUEMKnM8w5KL4Tnrp2gy88rRuQt6Ky8Bjml+Baw==", + "license": "MIT", + "dependencies": { + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" + } + }, + "node_modules/argparse/node_modules/underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha512-cp0oQQyZhUM1kpJDLdGO1jPZHgS/MpzoWYfe9+CM2h/QGDZlqwT2T3YGukuBdaNJ/CAPoeyAZRRHz8JFo176vA==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" + }, + "node_modules/aws-sign": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz", + "integrity": "sha512-6P7/Ls5F6++DsKu7iacris7qq/AZSWaX+gT4dtSyUxM82ePxWxaP7Slo82ZO3ZTx6GSKxQHAQlmFvM8e+Dd8ZA==", + "engines": { + "node": "*" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -243,6 +310,15 @@ "node": ">= 10.0.0" } }, + "node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "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", @@ -280,6 +356,27 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/boom": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.3.1.tgz", + "integrity": "sha512-xWrlXnkK46TjEW7HU5G39AXWuG5aiHz3++zk3bBzF4mfnVCkpcYbwsnLUqMmfZNgPEYS/AI8MH+vmJxH5Kz0PA==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dependencies": { + "hoek": "0.4.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/boom/node_modules/hoek": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.4.2.tgz", + "integrity": "sha512-Yj/N2TCrS0d8jvZgUpq9sDNt8/ABwTxPJW4+8QT0KXCMxOtRfUCUTEZEYyvMSgfDT3MGvwgO+NHfWPobagAIug==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -359,6 +456,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -393,6 +499,26 @@ "node": ">=10" } }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-table/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -402,6 +528,25 @@ "color-support": "bin.js" } }, + "node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha512-qfexlmLp9MyrkajQVyjEDb0Vj+KhRgR/rxLiVhaihlT+ZkX0lReqtH6Ack40CvMDERR4b5eFp3CreskpBs1Pig==", + "dependencies": { + "delayed-stream": "0.0.5" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -444,6 +589,14 @@ "node": ">= 0.6" } }, + "node_modules/cookie-jar": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cookie-jar/-/cookie-jar-0.2.0.tgz", + "integrity": "sha512-yImk9AY90xjoUsN2fWHoIhVgveXqiZv7LDqUTZEzVBHyzfay8AjcJITUZpz2fTYLh6rnP+7GogiuRCo/5j2epg==", + "engines": { + "node": "*" + } + }, "node_modules/cookie-parser": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", @@ -472,6 +625,12 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -485,6 +644,35 @@ "node": ">= 0.10" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/cryptiles": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.1.0.tgz", + "integrity": "sha512-WiOGszxSaVHd8T4hlu5Xcqs2uUYxbvotBP171ag2pLPKSwSKeTJYnzd98/YWV3jQYk/rpMHa3r01cQfN8SZrHQ==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dependencies": { + "boom": "0.3.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -511,6 +699,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha512-v+7uBd1pqe5YtgPacIIbZ8HuHeLFVNe4mUEyFDXL6KiqzEykjbw+5mXZXpGFgNVasdL4jWKgaKIXrEHiynN1LA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -546,9 +742,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -592,6 +788,62 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -628,6 +880,18 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha512-rp5dMKN8zEs9dfi9g0X1ClLmV//WRyk/R15mppFNICIFRG5P92VP7Z04p8pk++gABo9W2tY+kHyu6P1mEHgmTA==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -728,6 +992,14 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -759,6 +1031,32 @@ "node": ">= 0.8" } }, + "node_modules/forever-agent": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.2.0.tgz", + "integrity": "sha512-IasWSRIlfPnBZY1K9jEUK3PwsScR4mrcK+aNBJzGoPnW+S9b6f8I8ScyH4cehEOFNqnjGpP2gCaA22gqSV1xQA==", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.0.10.tgz", + "integrity": "sha512-Z9/PpT/agxXi80nMpOH6GFD7XOr6mwk5aWMxDt/KMY+Nm7e4FnRMjddM4/mLPJhpmp6alY1F/1JQpRE6z07xng==", + "dependencies": { + "async": "~0.2.7", + "combined-stream": "~0.0.4", + "mime": "~1.2.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -811,6 +1109,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -973,6 +1272,30 @@ "node": ">= 0.4" } }, + "node_modules/hawk": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-0.10.2.tgz", + "integrity": "sha512-BjpmnZ95odv7KOIsydfNTAxfGOGaVc6xbYL4fozWl45PWjDqskix0LHAekmGkpnrCAI6+AZRvJIXNTAllj+e6w==", + "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dependencies": { + "boom": "0.3.x", + "cryptiles": "0.1.x", + "hoek": "0.7.x", + "sntp": "0.1.x" + }, + "engines": { + "node": "0.8.x" + } + }, + "node_modules/hoek": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.7.6.tgz", + "integrity": "sha512-z75muWk69yyjWn6nNzJP0pnfgcewtSTs7uBolGUA7kWNdCYZukzHn3sYqUirhXul7qp9WBUwNT/7ieJZNveJqg==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "engines": { + "node": "0.8.x" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1025,6 +1348,15 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1125,6 +1457,12 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/jose": { "version": "4.15.9", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", @@ -1133,6 +1471,36 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-yaml": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.1.3.tgz", + "integrity": "sha512-2ElQ5tUBsI5GIjddfYGdudelD5+9JM9FfJXlrn+Mj3k72t4XrqBr3vf3+1sky0WKC3dSVhF0ZqIUpX9QFBmmfQ==", + "license": "MIT", + "dependencies": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-3.0.0.tgz", + "integrity": "sha512-VSSuxEAawKLYlCabQOR7YDijQ69zPqQBOriUuCgNhlAqtU7RPr41gPpaSs6WkEu+ZOtUequpXWbI51CS+Z/gMQ==", + "license": "BSD" + }, + "node_modules/log": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/log/-/log-1.4.0.tgz", + "integrity": "sha512-NnLhcxIAbhdhuMU0jDG83YjAH8JQj8tXUTy54Ib+4owuXwerrYFI8+OsnK1Ez/cig8O859QK6u6g0aYph/X/zQ==", + "engines": { + "node": ">= 0.2.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1166,6 +1534,20 @@ "node": ">= 0.4" } }, + "node_modules/MD5": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/MD5/-/MD5-1.3.0.tgz", + "integrity": "sha512-kDyDOgf7CECKouVCQ8tcbASPTyLMxrHxlvKBTBaXbF4eff5VYdfy4GCkCIIbAice4JGxBK+ExRD/4LdxDNXvLg==", + "deprecated": "deprecated, use lowercase 'md5@2.x' from now on", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1290,6 +1672,51 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "license": "MIT", + "dependencies": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/mysql/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/mysql/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1325,6 +1752,15 @@ } } }, + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", + "deprecated": "Use uuid module instead", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", @@ -1396,55 +1832,1979 @@ "node": ">=0.10.0" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", + "node_modules/npm": { + "version": "11.12.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.12.1.tgz", + "integrity": "sha512-zcoUuF1kezGSAo0CqtvoLXX3mkRqzuqYdL6Y5tdo8g69NVV3CkjQ6ZBhBgB4d7vGkPcV6TcvLi3GRKPDFX+xTA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which" + ], + "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^9.4.2", + "@npmcli/config": "^10.8.1", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.5", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.4", + "@sigstore/tuf": "^4.0.2", + "abbrev": "^4.0.0", + "archy": "~1.0.0", + "cacache": "^20.0.4", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^9.0.2", + "ini": "^6.0.0", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.3", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.1.5", + "libnpmexec": "^10.2.5", + "libnpmfund": "^7.0.19", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.1.5", + "libnpmpublish": "^11.1.3", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.3", + "make-fetch-happen": "^15.0.5", + "minimatch": "^10.2.4", + "minipass": "^7.1.3", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^12.2.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.1", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.5.0", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", + "qrcode-terminal": "^0.12.0", + "read": "^5.0.1", + "semver": "^7.7.4", + "spdx-expression-parse": "^4.0.0", + "ssri": "^13.0.1", + "supports-color": "^10.2.2", + "tar": "^7.5.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^2.0.2", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, "engines": { - "node": ">=0.10.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "node_modules/npm/node_modules/@gar/promise-retry": { + "version": "1.0.3", + "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "ee-first": "1.1.1" + "minipass": "^7.0.4" }, "engines": { - "node": ">= 0.8" + "node": ">=18.0.0" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "9.4.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "10.8.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "7.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "10.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.5.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/npm/node_modules/bin-links": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "20.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.6.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.4.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "5.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "8.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "13.0.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "9.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.7.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "8.2.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "10.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "6.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^5.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "10.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "8.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.4.2", + "@npmcli/installed-package-contents": "^4.0.0", + "binary-extensions": "^3.0.0", + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "10.2.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/arborist": "^9.4.2", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "read": "^5.0.1", + "semver": "^7.3.7", + "signal-exit": "^4.1.0", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "7.0.19", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.4.2" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "9.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.4.2", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "11.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7", + "sigstore": "^4.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "8.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "11.2.7", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "15.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "10.2.4", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "12.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "13.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "10.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "11.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "12.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "19.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "21.5.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^3.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.7.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.23", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "13.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "10.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "7.5.11", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "inBundle": 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/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/which": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.2.0.tgz", + "integrity": "sha512-4DtiD64CwPJ5vZ636j/KtM7DxWbX1KlkqwbqbEAxI3BCpBrQdrKOv8vC/36U6gfm1CVapy6QmcVxPnXPPQApTA==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "license": "MIT", "engines": { @@ -1586,6 +3946,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha512-yO5feByMzAp96LtP58wvPKSbaKAi/1C4kV9XpTctr6EepnP6F33RBNOiVrdz9BrPA98U2BMFsTNHo44TWcbQ2A==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -1626,24 +3995,31 @@ } }, "node_modules/prisma": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.0.1.tgz", - "integrity": "sha512-CaMNFHkf+DDq8zq3X/JJsQ4Koy7dyWwwtOKibkT/Am9j/tDxcfbg7+lB1Dzhx18G/+RQCMgjPYB61bhRqteNBQ==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "6.0.1" + "@prisma/engines": "5.22.0" }, "bin": { "prisma": "build/index.js" }, "engines": { - "node": ">=18.18" + "node": ">=16.13" }, "optionalDependencies": { "fsevents": "2.3.3" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1657,6 +4033,27 @@ "node": ">= 0.10" } }, + "node_modules/psql": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/psql/-/psql-0.0.1.tgz", + "integrity": "sha512-PmH2wP4vbKkIRTCJLQT6e18IONCYrwBKbfAm4cCn/r/0PIFG1S+iuHEba2Qm7y7bOeROLLyOuTGmInvQZhJzDw==", + "license": "ISC", + "dependencies": { + "cli-table": "^0.3.0", + "colors": "^0.6.2", + "MD5": "^1.2.1", + "mysql": "^2.4.1", + "q": "^1.0.1", + "underscore": "^1.6.0", + "winston": "^0.7.3", + "wordwrap": "0.0.2", + "yaml-config": "^0.3.0", + "yargs": "^1.2.6" + }, + "bin": { + "psql": "bin/psql.js" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -1664,6 +4061,17 @@ "dev": true, "license": "MIT" }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -1739,6 +4147,41 @@ "node": ">=8.10.0" } }, + "node_modules/request": { + "version": "2.16.6", + "resolved": "https://registry.npmjs.org/request/-/request-2.16.6.tgz", + "integrity": "sha512-TfD4kMo40kwuOpO7GYfAZpb2wYdw7yvTIglPNgPPSmp2Fz6MKNvPLla40FQ/ypdhy6B2jRNz3VlCjPD6mnzsmA==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "engines": [ + "node >= 0.8.0" + ], + "dependencies": { + "aws-sign": "~0.2.0", + "cookie-jar": "~0.2.0", + "forever-agent": "~0.2.0", + "form-data": "~0.0.3", + "hawk": "~0.10.2", + "json-stringify-safe": "~3.0.0", + "mime": "~1.2.7", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.2.0", + "qs": "~0.5.4", + "tunnel-agent": "~0.2.0" + } + }, + "node_modules/request/node_modules/mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==" + }, + "node_modules/request/node_modules/qs": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz", + "integrity": "sha512-KbOrQrP5Ye+0gmq+hwxoJwAFRwExACWqwxj1IDFFgqOw9Poxy3wwSbafd9ZqP6T6ykMfnxM573kt/a4i9ybatQ==", + "engines": { + "node": "*" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1975,6 +4418,128 @@ "semver": "bin/semver.js" } }, + "node_modules/sntp": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.1.2.tgz", + "integrity": "sha512-6fsOpJYQAQcO/UeW7T9mJwEenJymdU77o+gNiompGAammlSa+C49Oyt79ta/kgVbT13l4JAuKlo8FNvUnVjvEQ==", + "deprecated": "This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dependencies": { + "hoek": "0.4.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/sntp/node_modules/hoek": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.4.2.tgz", + "integrity": "sha512-Yj/N2TCrS0d8jvZgUpq9sDNt8/ABwTxPJW4+8QT0KXCMxOtRfUCUTEZEYyvMSgfDT3MGvwgO+NHfWPobagAIug==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "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==", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io-parser": { + "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==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -1984,6 +4549,24 @@ "node": ">= 10.x" } }, + "node_modules/sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2096,6 +4679,14 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tunnel-agent": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.2.0.tgz", + "integrity": "sha512-PXy4q1PH88BK0pcGOEMXFAslyBuRWz1wxLfPXTlYFd41eyUgjOALaVGbWJN1ymjbnBzjWunVSKmrrMMh8oLaZA==", + "engines": { + "node": "*" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2128,6 +4719,25 @@ "dev": true, "license": "MIT" }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT" + }, + "node_modules/underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha512-yxkabuCaIBnzfIvX3kBxQqCs0ar/bfJwDnFEHJUm/ZrRVhT3IItdRF5cZjARLzEnyQYtIUhsZ2LG2j3HidFOFQ==", + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2186,12 +4796,58 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/winston": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-0.7.3.tgz", + "integrity": "sha512-iVTT8tf9YnTyfZX+aEUj2fl6WBRet7za6vdjMeyF8SA80Vii2rreM5XH+5qmpBV9uJGj8jz8BozvTDcroVq/eA==", + "dependencies": { + "async": "0.2.x", + "colors": "0.6.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "pkginfo": "0.3.x", + "request": "2.16.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", + "license": "MIT/X11", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -2207,6 +4863,24 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/yaml-config": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yaml-config/-/yaml-config-0.3.0.tgz", + "integrity": "sha512-YfFYC7ijNRSO5Ii0vMw7sUzqqrnkv010Iz5s7qnwErVTy3dKQraVbZFOQGBrT0jbxVlauIqfrHSJDZ+gzd0aaw==", + "dependencies": { + "js-yaml": "~2.1.0", + "log": "~1.4.0" + }, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/yargs": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz", + "integrity": "sha512-7OGt4xXoWJQh5ulgZ78rKaqY7dNWbjfK+UKxGcIlaM2j7C4fqGchyv8CPvEWdRPrHp6Ula/YU8yGRpYGOHrI+g==", + "license": "MIT/X11" + }, "node_modules/zod": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", diff --git a/backend/package.json b/backend/package.json index 225bda6..fad8bfe 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,6 @@ "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "^6.0.1", "bcrypt": "^5.1.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -21,10 +20,15 @@ "express-oauth2-jwt-bearer": "^1.6.0", "express-session": "^1.18.1", "pg": "^8.10.0", - "prisma": "^6.0.1", + "psql": "^0.0.1", + "socket.io": "^4.8.3", "zod": "^3.22.4" }, "devDependencies": { - "nodemon": "^2.0.22" + "@prisma/client": "^5.22.0", + "i": "^0.3.7", + "nodemon": "^2.0.22", + "npm": "^11.12.1", + "prisma": "^5.22.0" } } diff --git a/backend/prisma/migrations/20260216210640_add_form_tables/migration.sql b/backend/prisma/migrations/20260216210640_add_form_tables/migration.sql new file mode 100644 index 0000000..bc092bb --- /dev/null +++ b/backend/prisma/migrations/20260216210640_add_form_tables/migration.sql @@ -0,0 +1,56 @@ +-- CreateEnum +CREATE TYPE "question_type" AS ENUM ('TEXT', 'MCQ', 'MULTIPLE_CHOICE', 'CHECKBOXES', 'DROPDOWNS', 'STAR_RATINGS'); + +-- CreateTable +CREATE TABLE "forms" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "title" TEXT, + "description" TEXT NOT NULL, + "created_by" TEXT, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "forms_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "questions" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "form_id" UUID, + "question_text" TEXT NOT NULL DEFAULT '', + "question_type" "question_type", + + CONSTRAINT "questions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "submissions" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "form_id" UUID, + + CONSTRAINT "submissions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "responses" ( + "id" BIGSERIAL NOT NULL, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "submission_id" UUID, + "question_id" UUID, + "answer" TEXT DEFAULT '', + + CONSTRAINT "responses_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "questions" ADD CONSTRAINT "questions_form_id_fkey" FOREIGN KEY ("form_id") REFERENCES "forms"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "submissions" ADD CONSTRAINT "submissions_form_id_fkey" FOREIGN KEY ("form_id") REFERENCES "forms"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "responses" ADD CONSTRAINT "responses_submission_id_fkey" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "responses" ADD CONSTRAINT "responses_question_id_fkey" FOREIGN KEY ("question_id") REFERENCES "questions"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/20260318145536_add_new_tables/migration.sql b/backend/prisma/migrations/20260318145536_add_new_tables/migration.sql new file mode 100644 index 0000000..7f92660 --- /dev/null +++ b/backend/prisma/migrations/20260318145536_add_new_tables/migration.sql @@ -0,0 +1,304 @@ +/* + Warnings: + + - The values [CHECKBOXES,DROPDOWNS,STAR_RATINGS] on the enum `question_type` will be removed. If these variants are still used in the database, this will fail. + - You are about to drop the column `degree` on the `UserEducation` table. All the data in the column will be lost. + - A unique constraint covering the columns `[clubId]` on the table `User` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateEnum +CREATE TYPE "ReactionType" AS ENUM ('LIKE', 'CELEBRATE', 'SUPPORT'); + +-- CreateEnum +CREATE TYPE "FriendRequestStatus" AS ENUM ('PENDING', 'ACCEPTED', 'REJECTED'); + +-- AlterEnum +ALTER TYPE "UserRole" ADD VALUE 'CONVENER'; + +-- AlterEnum +BEGIN; +CREATE TYPE "question_type_new" AS ENUM ('TEXT', 'MCQ', 'MULTIPLE_CHOICE', 'CHECKBOX', 'DROPDOWN', 'RATING', 'LONG_TEXT'); +ALTER TABLE "questions" ALTER COLUMN "question_type" TYPE "question_type_new" USING ("question_type"::text::"question_type_new"); +ALTER TYPE "question_type" RENAME TO "question_type_old"; +ALTER TYPE "question_type_new" RENAME TO "question_type"; +DROP TYPE "question_type_old"; +COMMIT; + +-- AlterTable +ALTER TABLE "Application" ADD COLUMN "attendance" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "clubId" INTEGER; + +-- AlterTable +ALTER TABLE "UserEducation" DROP COLUMN "degree"; + +-- AlterTable +ALTER TABLE "questions" ADD COLUMN "is_required" BOOLEAN DEFAULT false, +ADD COLUMN "options" TEXT[], +ADD COLUMN "sort_order" INTEGER DEFAULT 0; + +-- CreateTable +CREATE TABLE "Club" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "logoUrl" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Club_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Project" ( + "id" SERIAL NOT NULL, + "clubId" INTEGER NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "githubUrl" TEXT, + "demoUrl" TEXT, + "contributors" TEXT[], + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Project_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PostComment" ( + "id" SERIAL NOT NULL, + "projectId" INTEGER NOT NULL, + "userId" TEXT NOT NULL, + "content" TEXT NOT NULL, + "parentId" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PostComment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Post" ( + "id" TEXT NOT NULL, + "content" TEXT NOT NULL, + "authorId" TEXT NOT NULL, + "eventId" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Post_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PostImage" ( + "id" TEXT NOT NULL, + "postId" TEXT NOT NULL, + "url" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PostImage_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Tag" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + + CONSTRAINT "Tag_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PostTag" ( + "postId" TEXT NOT NULL, + "tagId" TEXT NOT NULL, + + CONSTRAINT "PostTag_pkey" PRIMARY KEY ("postId","tagId") +); + +-- CreateTable +CREATE TABLE "Reaction" ( + "id" TEXT NOT NULL, + "postId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "type" "ReactionType" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Reaction_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Comment" ( + "id" TEXT NOT NULL, + "content" TEXT NOT NULL, + "postId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "parentCommentId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "projectId" INTEGER, + + CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FriendRequest" ( + "id" TEXT NOT NULL, + "senderId" TEXT NOT NULL, + "receiverId" TEXT NOT NULL, + "status" "FriendRequestStatus" NOT NULL DEFAULT 'PENDING', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FriendRequest_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Friendship" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "friendId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Friendship_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Follow" ( + "id" TEXT NOT NULL, + "followerId" TEXT NOT NULL, + "followingId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Follow_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Conversation" ( + "id" TEXT NOT NULL, + "isGroup" BOOLEAN NOT NULL DEFAULT false, + "name" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Conversation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ConversationMember" ( + "id" TEXT NOT NULL, + "conversationId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ConversationMember_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Message" ( + "id" TEXT NOT NULL, + "conversationId" TEXT NOT NULL, + "senderId" TEXT NOT NULL, + "content" TEXT NOT NULL, + "isRead" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Message_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Reaction_postId_userId_key" ON "Reaction"("postId", "userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "FriendRequest_senderId_receiverId_key" ON "FriendRequest"("senderId", "receiverId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Friendship_userId_friendId_key" ON "Friendship"("userId", "friendId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Follow_followerId_followingId_key" ON "Follow"("followerId", "followingId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ConversationMember_conversationId_userId_key" ON "ConversationMember"("conversationId", "userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_clubId_key" ON "User"("clubId"); + +-- AddForeignKey +ALTER TABLE "User" ADD CONSTRAINT "User_clubId_fkey" FOREIGN KEY ("clubId") REFERENCES "Club"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Project" ADD CONSTRAINT "Project_clubId_fkey" FOREIGN KEY ("clubId") REFERENCES "Club"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "PostComment"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Post" ADD CONSTRAINT "Post_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostImage" ADD CONSTRAINT "PostImage_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostTag" ADD CONSTRAINT "PostTag_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostTag" ADD CONSTRAINT "PostTag_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Reaction" ADD CONSTRAINT "Reaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_parentCommentId_fkey" FOREIGN KEY ("parentCommentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FriendRequest" ADD CONSTRAINT "FriendRequest_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FriendRequest" ADD CONSTRAINT "FriendRequest_receiverId_fkey" FOREIGN KEY ("receiverId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Friendship" ADD CONSTRAINT "Friendship_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Friendship" ADD CONSTRAINT "Friendship_friendId_fkey" FOREIGN KEY ("friendId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Follow" ADD CONSTRAINT "Follow_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Follow" ADD CONSTRAINT "Follow_followingId_fkey" FOREIGN KEY ("followingId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ConversationMember" ADD CONSTRAINT "ConversationMember_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ConversationMember" ADD CONSTRAINT "ConversationMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Message" ADD CONSTRAINT "Message_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Message" ADD CONSTRAINT "Message_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 46a1dd6..cc301b0 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -3,19 +3,20 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") } model User { - id String @id @default(cuid()) - auth0Id String @unique - email String @unique - username String @unique - role UserRole @default(ORGANIZER) - status UserStatus @default(ACTIVE) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + auth0Id String @unique + email String @unique + username String @unique + role UserRole @default(ORGANIZER) + status UserStatus @default(ACTIVE) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt profile UserProfile? education UserEducation[] experience UserExperience[] @@ -24,11 +25,27 @@ model User { applications Application[] createdEvents Event[] teamMembers TeamMember[] + clubId Int? @unique + club Club? @relation("ClubConvener", fields: [clubId], references: [id]) + + // Community Module Relations + posts Post[] + reactions Reaction[] + friendships Friendship[] @relation("UserFriendships") + friendOf Friendship[] @relation("FriendUser") + sentRequests FriendRequest[] @relation("SentRequests") + receivedRequests FriendRequest[] @relation("ReceivedRequests") + followers Follow[] @relation("UserFollowers") + following Follow[] @relation("UserFollowing") + conversations ConversationMember[] + messages Message[] + postcomment PostComment[] + Comment Comment[] } model UserProfile { - id Int @id @default(autoincrement()) - userId String @unique + id Int @id @default(autoincrement()) + userId String @unique firstName String? lastName String? avatarUrl String? @@ -37,20 +54,19 @@ model UserProfile { phone String? country String? city String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model UserEducation { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) userId String institutionName String - degree String fieldOfStudy String graduationYear Int? - createdAt DateTime @default(now()) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + createdAt DateTime @default(now()) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model UserExperience { @@ -82,32 +98,73 @@ model UserSocialProfile { user User @relation(fields: [userId], references: [id], onDelete: Cascade) } +model Club { + id Int @id @default(autoincrement()) + name String + description String? + logoUrl String? + createdAt DateTime @default(now()) + convener User? @relation("ClubConvener") + projects Project[] +} + +model Project { + id Int @id @default(autoincrement()) + clubId Int + title String + description String + githubUrl String? + demoUrl String? + contributors String[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + club Club @relation(fields: [clubId], references: [id], onDelete: Cascade) + comments Comment[] + postcomment PostComment[] +} + +model PostComment { + id Int @id @default(autoincrement()) + projectId Int + userId String + content String + parentId Int? + createdAt DateTime @default(now()) + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id]) + parent PostComment? @relation("PostCommentToReplies", fields: [parentId], references: [id]) + replies PostComment[] @relation("PostCommentToReplies") + Post Post? @relation(fields: [postId], references: [id]) + postId String? +} + model Event { - id Int @id @default(autoincrement()) - name String - type EventType - tagline String? - about String? - maxParticipants Int? - minTeamSize Int? - maxTeamSize Int? - mode EventMode @default(ONLINE) - status EventStatus @default(DRAFT) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - createdById String - createdBy User @relation(fields: [createdById], references: [id]) - timeline EventTimeline? - links EventLink? - branding EventBranding? - tracks Track[] - sponsors Sponsor[] - eventPeople EventPerson[] - applications Application[] - applicationForm ApplicationForm? - customQuestions CustomQuestion[] - teams Team[] - projects ProjectSubmission[] + id Int @id @default(autoincrement()) + name String + type EventType + tagline String? + about String? + maxParticipants Int? + minTeamSize Int? + maxTeamSize Int? + mode EventMode @default(ONLINE) + status EventStatus @default(DRAFT) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + createdById String + createdBy User @relation(fields: [createdById], references: [id]) + timeline EventTimeline? + links EventLink? + branding EventBranding? + tracks Track[] + sponsors Sponsor[] + eventPeople EventPerson[] + applications Application[] + applicationForm ApplicationForm? + customQuestions CustomQuestion[] + teams Team[] + projects ProjectSubmission[] + posts Post[] // For event invite cards embedded in posts } model ApplicationForm { @@ -132,14 +189,14 @@ model CustomQuestion { } model EventTimeline { - id Int @id @default(autoincrement()) - eventId Int @unique - eventStart DateTime - eventEnd DateTime - applicationsStart DateTime? - applicationsEnd DateTime? - rsvpDaysBeforeDeadline Int? - event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) + id Int @id @default(autoincrement()) + eventId Int @unique + eventStart DateTime + eventEnd DateTime + applicationsStart DateTime? + applicationsEnd DateTime? + rsvpDaysBeforeDeadline Int? + event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) } model EventLink { @@ -148,17 +205,17 @@ model EventLink { websiteUrl String? micrositeUrl String? contactEmail String? - socialLinks Json? // For storing multiple social media links + socialLinks Json? event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) } model EventBranding { - id Int @id @default(autoincrement()) - eventId Int @unique - logoUrl String? - coverUrl String? + id Int @id @default(autoincrement()) + eventId Int @unique + logoUrl String? + coverUrl String? brandColor String? - event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) + event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) } model Track { @@ -180,13 +237,13 @@ model Prize { } model Sponsor { - id Int @id @default(autoincrement()) - eventId Int - name String - logoUrl String? - websiteUrl String? - tier SponsorTier - event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) + id Int @id @default(autoincrement()) + eventId Int + name String + logoUrl String? + websiteUrl String? + tier SponsorTier + event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) } model EventPerson { @@ -194,19 +251,19 @@ model EventPerson { eventId Int name String role EventPersonRole - imageUrl String? - description String? - socialLinks Json? - event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) + imageUrl String? + description String? + socialLinks Json? + event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) } model Team { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) eventId Int name String - hashCode String @unique - createdAt DateTime @default(now()) - event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) + hashCode String @unique + createdAt DateTime @default(now()) + event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) members TeamMember[] applications Application[] projects ProjectSubmission[] @@ -222,17 +279,18 @@ model TeamMember { } model Application { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) eventId Int userId String teamId Int? status ApplicationStatus - createdAt DateTime @default(now()) - event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id]) - team Team? @relation(fields: [teamId], references: [id]) + createdAt DateTime @default(now()) + attendance Boolean @default(false) userData Json responses Json + event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id]) + team Team? @relation(fields: [teamId], references: [id]) } model ProjectSubmission { @@ -249,9 +307,61 @@ model ProjectSubmission { team Team @relation(fields: [teamId], references: [id]) } +model Form { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + title String? + description String + createdBy String? @map("created_by") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + questions Question[] + submissions Submission[] + + @@map("forms") +} + +model Question { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + formId String? @map("form_id") @db.Uuid + questionText String @default("") @map("question_text") + questionType QuestionType? @map("question_type") + options String[] + is_required Boolean? @default(false) + sort_order Int? @default(0) + form Form? @relation(fields: [formId], references: [id]) + responses Response[] + + @@map("questions") +} + +model Submission { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + formId String? @map("form_id") @db.Uuid + responses Response[] + form Form? @relation(fields: [formId], references: [id]) + + @@map("submissions") +} + +model Response { + id BigInt @id @default(autoincrement()) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + submissionId String? @map("submission_id") @db.Uuid + questionId String? @map("question_id") @db.Uuid + answer String? @default("") + question Question? @relation(fields: [questionId], references: [id]) + submission Submission? @relation(fields: [submissionId], references: [id]) + + @@map("responses") +} + +// ─── Enums ──────────────────────────────────────────────────────────────────── + enum UserRole { ADMIN ORGANIZER + CONVENER } enum UserStatus { @@ -320,3 +430,176 @@ enum TeamMemberRole { LEADER MEMBER } + +// ========================================== +// Community Module Models +// ========================================== + +model Post { + id String @id @default(cuid()) + content String @db.Text + authorId String + eventId Int? // Optional event link for invites + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + author User @relation(fields: [authorId], references: [id], onDelete: Cascade) + event Event? @relation(fields: [eventId], references: [id], onDelete: SetNull) + images PostImage[] + tags PostTag[] + reactions Reaction[] + postcomment PostComment[] + Comment Comment[] +} + +model PostImage { + id String @id @default(cuid()) + postId String + url String + createdAt DateTime @default(now()) + + post Post @relation(fields: [postId], references: [id], onDelete: Cascade) +} + +model Tag { + id String @id @default(cuid()) + name String @unique + posts PostTag[] +} + +model PostTag { + postId String + tagId String + + post Post @relation(fields: [postId], references: [id], onDelete: Cascade) + tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) + + @@id([postId, tagId]) +} + +model Reaction { + id String @id @default(cuid()) + postId String + userId String + type ReactionType + createdAt DateTime @default(now()) + + post Post @relation(fields: [postId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([postId, userId]) // User can only react once per post +} + +model Comment { + id String @id @default(cuid()) + content String @db.Text + postId String + userId String + parentCommentId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + post Post @relation(fields: [postId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + parentComment Comment? @relation("ThreadedComments", fields: [parentCommentId], references: [id], onDelete: Cascade) + replies Comment[] @relation("ThreadedComments") + Project Project? @relation(fields: [projectId], references: [id]) + projectId Int? +} + +model FriendRequest { + id String @id @default(cuid()) + senderId String + receiverId String + status FriendRequestStatus @default(PENDING) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + sender User @relation("SentRequests", fields: [senderId], references: [id], onDelete: Cascade) + receiver User @relation("ReceivedRequests", fields: [receiverId], references: [id], onDelete: Cascade) + + @@unique([senderId, receiverId]) +} + +model Friendship { + id String @id @default(cuid()) + userId String + friendId String + createdAt DateTime @default(now()) + + user User @relation("UserFriendships", fields: [userId], references: [id], onDelete: Cascade) + friend User @relation("FriendUser", fields: [friendId], references: [id], onDelete: Cascade) + + @@unique([userId, friendId]) +} + +model Follow { + id String @id @default(cuid()) + followerId String + followingId String + createdAt DateTime @default(now()) + + follower User @relation("UserFollowers", fields: [followerId], references: [id], onDelete: Cascade) + following User @relation("UserFollowing", fields: [followingId], references: [id], onDelete: Cascade) + + @@unique([followerId, followingId]) +} + +model Conversation { + id String @id @default(cuid()) + isGroup Boolean @default(false) + name String? // Optional for groups + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + members ConversationMember[] + messages Message[] +} + +model ConversationMember { + id String @id @default(cuid()) + conversationId String + userId String + joinedAt DateTime @default(now()) + + conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([conversationId, userId]) +} + +model Message { + id String @id @default(cuid()) + conversationId String + senderId String + content String @db.Text + isRead Boolean @default(false) + createdAt DateTime @default(now()) + + conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) + sender User @relation(fields: [senderId], references: [id], onDelete: Cascade) +} + +enum ReactionType { + LIKE + CELEBRATE + SUPPORT +} + +enum FriendRequestStatus { + PENDING + ACCEPTED + REJECTED +} + +enum QuestionType { + TEXT + MCQ + MULTIPLE_CHOICE + CHECKBOX + DROPDOWN + RATING + LONG_TEXT + + @@map("question_type") +} diff --git a/backend/prisma_err.txt b/backend/prisma_err.txt new file mode 100644 index 0000000..8c68b4d --- /dev/null +++ b/backend/prisma_err.txt @@ -0,0 +1,110 @@ +Environment variables loaded from .env +Prisma schema loaded from prisma\schema.prisma +node.exe : +At C:\Program +Files\nodejs\npx.ps1:29 +char:3 ++ & $NODE_EXE +$NPX_CLI_JS $args ++ ~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~ + + CategoryInfo + : NotSpecified: (: + String) [], RemoteExc + eption + + FullyQualifiedError + Id : NativeCommandErr + or + +Error: Prisma schema +validation - (validate +wasm) +Error code: P1012 +[1;91merror[0m: +[1mError validating +field `postcomment` in +model `Post`: The +relation field +`postcomment` on model +`Post` is missing an +opposite relation field +on the model +`PostComment`. Either run +`prisma format` or add it +manually.[0m + [1;94m-->[0m [4mpris +ma\schema.prisma:448[0m +[1;94m | [0m +[1;94m447 | [0m +reactions Reaction[] +[1;94m448 | [0m +[1;91mpostcomment +PostComment[][0m +[1;94m449 | [0m} +[1;94m | [0m +[1;91merror[0m: +[1mError validating +field `post` in model +`Comment`: The relation +field `post` on model +`Comment` is missing an +opposite relation field +on the model `Post`. +Either run `prisma +format` or add it +manually.[0m + [1;94m-->[0m [4mpris +ma\schema.prisma:498[0m +[1;94m | [0m +[1;94m497 | [0m +[1;94m498 | [0m +[1;91mpost Post + @relation(fields: +[postId], references: +[id], onDelete: +Cascade)[0m +[1;94m499 | [0m user + User +@relation(fields: +[userId], references: +[id], onDelete: Cascade) +[1;94m | [0m +[1;91merror[0m: +[1mError validating +field `user` in model +`Comment`: The relation +field `user` on model +`Comment` is missing an +opposite relation field +on the model `User`. +Either run `prisma +format` or add it +manually.[0m + [1;94m-->[0m [4mpris +ma\schema.prisma:499[0m +[1;94m | [0m +[1;94m498 | [0m post + Post +@relation(fields: +[postId], references: +[id], onDelete: Cascade) +[1;94m499 | [0m +[1;91muser User + @relation(fields: +[userId], references: +[id], onDelete: +Cascade)[0m +[1;94m500 | [0m +parentComment Comment? @r +elation("ThreadedComments" +, fields: +[parentCommentId], +references: [id], +onDelete: Cascade) +[1;94m | [0m + +Validation Error Count: 3 +[Context: validate] + +Prisma CLI Version : +5.22.0 diff --git a/backend/server.js b/backend/server.js index cf29271..4f631d0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -5,6 +5,8 @@ import cookieParser from 'cookie-parser'; import errorHandler from './src/middleware/error.js'; import prisma from './src/config/database.js'; import { checkJwt } from './src/middleware/auth.js'; +import { createServer } from 'http'; +import { Server } from 'socket.io'; // Import routes import authRoutes from './src/routes/auth.js'; @@ -12,15 +14,39 @@ import eventRoutes from './src/routes/events.js'; import teamRoutes from './src/routes/teams.js'; import projectRoutes from './src/routes/projects.js'; import profileRoutes from './src/routes/profiles.js'; +import clubRoutes from './src/routes/clubs.js'; +import postRoutes from './src/routes/posts.js'; +import friendRoutes from './src/routes/friends.js'; +import messageRoutes from './src/routes/messages.js'; const app = express(); +const httpServer = createServer(app); const PORT = process.env.PORT || 4000; +const io = new Server(httpServer, { + cors: { + origin: process.env.CORS_ORIGIN || 'http://localhost:3000', + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] + } +}); + +// Make io accessible in our routes +app.set('socketio', io); + +io.on('connection', (socket) => { + // console.log('Client connected:', socket.id); + + socket.on('disconnect', () => { + // console.log('Client disconnected:', socket.id); + }); +}); + // Middleware app.use(cors({ origin: process.env.CORS_ORIGIN || 'http://localhost:3000', credentials: true, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], allowedHeaders: ['Content-Type', 'Authorization'] })); app.use(express.json()); @@ -31,13 +57,17 @@ app.use('/api/auth', authRoutes); app.use('/api/events', eventRoutes); // Remove checkJwt from public routes app.use('/api/teams', checkJwt, teamRoutes); app.use('/api/projects', checkJwt, projectRoutes); +app.use('/api/clubs', clubRoutes); app.use('/api', checkJwt, profileRoutes); // Changed from '/api/profiles' to '/api' to match frontend calls +app.use('/api/posts', postRoutes); +app.use('/api/friends', friendRoutes); +app.use('/api/messages', messageRoutes); // Error handling app.use(errorHandler); // Start server -app.listen(PORT, () => { +const server = httpServer.listen(PORT, () => { console.log(`Server running on port ${PORT}`); console.log(`- Local: http://localhost:${PORT}`); if (process.env.SERVER_URL) { diff --git a/backend/src/app.js b/backend/src/app.js index 860adf3..2f21171 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -1,5 +1,5 @@ app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], // Add PATCH to allowed methods + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], })); \ No newline at end of file diff --git a/backend/src/config/database.js b/backend/src/config/database.js index 64605f8..b2e2d68 100644 --- a/backend/src/config/database.js +++ b/backend/src/config/database.js @@ -8,7 +8,7 @@ const prisma = new PrismaClient({ } }, // Add log level for debugging - log: ['query', 'info', 'warn', 'error'] + // log: ['query', 'info', 'warn', 'error'] }); // Alternative connection method with retry logic diff --git a/backend/src/controllers/auth.js b/backend/src/controllers/auth.js index e449f99..121b3a5 100644 --- a/backend/src/controllers/auth.js +++ b/backend/src/controllers/auth.js @@ -73,7 +73,7 @@ export const register = async (req, res) => { export const getCurrentUser = async (req, res) => { try { - console.log(req.auth.payload.sub); + // console.log(req.auth.payload.sub); const user = await prisma.user.findUnique({ where: { auth0Id: req.auth.payload.sub }, include: { @@ -84,7 +84,7 @@ export const getCurrentUser = async (req, res) => { socialProfiles: true } }); - console.log(user) + // console.log(user) if (!user) { return res.status(404).json({ error: 'User not found' }); diff --git a/backend/src/controllers/clubs.js b/backend/src/controllers/clubs.js new file mode 100644 index 0000000..c489a1c --- /dev/null +++ b/backend/src/controllers/clubs.js @@ -0,0 +1,239 @@ +import prisma from '../config/database.js'; + +export const getClubs = async (req, res) => { + try { + const clubs = await prisma.club.findMany({ + include: { + convener: { + select: { id: true, username: true } + }, + projects: { + select: { id: true } + } + }, + orderBy: { createdAt: 'desc' } + }); + res.json(clubs); + } catch (err) { + console.error('Error fetching clubs:', err); + res.status(500).json({ error: 'Failed to fetch clubs' }); + } +}; + +export const getClubById = async (req, res) => { + try { + const { id } = req.params; + const club = await prisma.club.findUnique({ + where: { id: parseInt(id) }, + include: { + convener: { + select: { id: true, username: true } + }, + projects: { + include: { + comments: { + where: { parentId: null }, + include: { + user: { select: { id: true, username: true } }, + replies: { + include: { + user: { select: { id: true, username: true } }, + replies: { + include: { + user: { select: { id: true, username: true } } + } + } + } + } + }, + orderBy: { createdAt: 'desc' } + } + }, + orderBy: { createdAt: 'desc' } + } + } + }); + if (!club) return res.status(404).json({ error: 'Club not found' }); + res.json(club); + } catch (err) { + console.error('Error fetching club:', err); + res.status(500).json({ error: 'Failed to fetch club' }); + } +}; + +export const createProject = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const { title, description, githubUrl, demoUrl, contributors } = req.body; + + const user = await prisma.user.findUnique({ + where: { auth0Id }, + include: { club: true } + }); + + if (!user) return res.status(404).json({ error: 'User not found' }); + if (user.role !== 'CONVENER') return res.status(403).json({ error: 'Only conveners can add projects' }); + if (!user.club) return res.status(403).json({ error: 'You are not assigned to a club' }); + + const project = await prisma.project.create({ + data: { + clubId: user.club.id, + title, + description, + githubUrl: githubUrl || null, + demoUrl: demoUrl || null, + contributors: contributors || [] + } + }); + + res.status(201).json(project); + } catch (err) { + console.error('Error creating project:', err); + res.status(500).json({ error: 'Failed to create project' }); + } +}; + +export const updateProject = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const { id } = req.params; + const { title, description, githubUrl, demoUrl, contributors } = req.body; + + const user = await prisma.user.findUnique({ + where: { auth0Id }, + include: { club: true } + }); + + if (!user) return res.status(404).json({ error: 'User not found' }); + if (user.role !== 'CONVENER') return res.status(403).json({ error: 'Only conveners can edit projects' }); + + const project = await prisma.project.findUnique({ where: { id: parseInt(id) } }); + if (!project) return res.status(404).json({ error: 'Project not found' }); + if (project.clubId !== user.club?.id) return res.status(403).json({ error: 'You can only edit your own club projects' }); + + const updated = await prisma.project.update({ + where: { id: parseInt(id) }, + data: { title, description, githubUrl, demoUrl, contributors } + }); + + res.json(updated); + } catch (err) { + console.error('Error updating project:', err); + res.status(500).json({ error: 'Failed to update project' }); + } +}; + +export const deleteProject = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const { id } = req.params; + + const user = await prisma.user.findUnique({ + where: { auth0Id }, + include: { club: true } + }); + + if (!user) return res.status(404).json({ error: 'User not found' }); + if (user.role !== 'CONVENER') return res.status(403).json({ error: 'Only conveners can delete projects' }); + + const project = await prisma.project.findUnique({ where: { id: parseInt(id) } }); + if (!project) return res.status(404).json({ error: 'Project not found' }); + if (project.clubId !== user.club?.id) return res.status(403).json({ error: 'You can only delete your own club projects' }); + + await prisma.project.delete({ where: { id: parseInt(id) } }); + res.json({ message: 'Project deleted successfully' }); + } catch (err) { + console.error('Error deleting project:', err); + res.status(500).json({ error: 'Failed to delete project' }); + } +}; + +export const getComments = async (req, res) => { + try { + const { projectId } = req.params; + + const comments = await prisma.comment.findMany({ + where: { + projectId: parseInt(projectId), + parentId: null + }, + include: { + user: { select: { id: true, username: true } }, + replies: { + include: { + user: { select: { id: true, username: true } }, + replies: { + include: { + user: { select: { id: true, username: true } } + } + } + }, + orderBy: { createdAt: 'asc' } + } + }, + orderBy: { createdAt: 'desc' } + }); + + res.json(comments); + } catch (err) { + console.error('Error fetching comments:', err); + res.status(500).json({ error: 'Failed to fetch comments' }); + } +}; + +export const createComment = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const { projectId } = req.params; + const { content, parentId } = req.body; + + const user = await prisma.user.findUnique({ where: { auth0Id } }); + if (!user) return res.status(404).json({ error: 'User not found' }); + + if (!content?.trim()) return res.status(400).json({ error: 'Comment cannot be empty' }); + + const comment = await prisma.comment.create({ + data: { + projectId: parseInt(projectId), + userId: user.id, + content: content.trim(), + parentId: parentId ? parseInt(parentId) : null + }, + include: { + user: { select: { id: true, username: true } }, + replies: { + include: { + user: { select: { id: true, username: true } } + } + } + } + }); + + res.status(201).json(comment); + } catch (err) { + console.error('Error creating comment:', err); + res.status(500).json({ error: 'Failed to create comment' }); + } +}; + +export const deleteComment = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const { id } = req.params; + + const user = await prisma.user.findUnique({ where: { auth0Id } }); + if (!user) return res.status(404).json({ error: 'User not found' }); + + const comment = await prisma.comment.findUnique({ where: { id: parseInt(id) } }); + if (!comment) return res.status(404).json({ error: 'Comment not found' }); + if (comment.userId !== user.id) return res.status(403).json({ error: 'You can only delete your own comments' }); + + await prisma.comment.deleteMany({ where: { parentId: parseInt(id) } }); + await prisma.comment.delete({ where: { id: parseInt(id) } }); + + res.json({ message: 'Comment deleted' }); + } catch (err) { + console.error('Error deleting comment:', err); + res.status(500).json({ error: 'Failed to delete comment' }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/events.js b/backend/src/controllers/events.js index 29093e1..b88bea8 100644 --- a/backend/src/controllers/events.js +++ b/backend/src/controllers/events.js @@ -1,4 +1,5 @@ import prisma from '../config/database.js'; +//import { createNotification } from "../utils/notification.js"; export const createEvent = async (req, res) => { try { @@ -20,16 +21,16 @@ export const createEvent = async (req, res) => { } // delete previous autosave - if(req.body.id){ - const eventDelete = await prisma.event.delete({ - where : { id : req.body.id} + if (req.body.id) { + await prisma.event.delete({ + where: { id: req.body.id } }); } - console.log('Full eventData:', JSON.stringify(eventData, null, 2)); - console.log('Event Branding Data:', JSON.stringify(eventData.eventBranding, null, 2)); - console.log('Logo Image Public URL:', eventData.eventBranding?.logoImage?.publicUrl); - console.log('Cover Image Public URL:', eventData.eventBranding?.coverImage?.publicUrl); + // console.log('Full eventData:', JSON.stringify(eventData, null, 2)); + // console.log('Event Branding Data:', JSON.stringify(eventData.eventBranding, null, 2)); + // console.log('Logo Image Public URL:', eventData.eventBranding?.logoImage?.publicUrl); + // console.log('Cover Image Public URL:', eventData.eventBranding?.coverImage?.publicUrl); const event = await prisma.event.create({ data: { @@ -43,7 +44,7 @@ export const createEvent = async (req, res) => { mode: eventData.mode, status: eventData.status || 'PUBLISHED', createdById: user.id, - + timeline: { create: { eventStart: eventData.eventTimeline.eventStart, @@ -53,15 +54,15 @@ export const createEvent = async (req, res) => { rsvpDaysBeforeDeadline: parseInt(eventData.eventTimeline.rsvpDaysBeforeDeadline) } }, - + branding: { create: { - logoUrl: eventData.eventBranding.logoImage || null, + logoUrl: eventData.eventBranding.logoImage || null, coverUrl: eventData.eventBranding.coverImage || null, brandColor: eventData.eventBranding.brandColor || '#000000' } }, - + links: { create: { websiteUrl: eventData.eventLinks[0].websiteUrl || null, @@ -70,7 +71,7 @@ export const createEvent = async (req, res) => { socialLinks: eventData.eventLinks[0].socialLinks || {} } }, - + tracks: { create: eventData.tracks.map(track => ({ name: track.name, @@ -84,22 +85,22 @@ export const createEvent = async (req, res) => { } })) }, - + sponsors: { create: eventData.sponsors.map(sponsor => ({ name: sponsor.name, websiteUrl: sponsor.websiteUrl || '', logoUrl: sponsor.logoUrl || null, - tier: sponsor.tier || 'GOLD' // Assuming GOLD is a valid tier in your schema + tier: sponsor.tier || 'GOLD' })) }, - + eventPeople: { create: eventData.eventPeople.map(person => ({ name: person.name, role: person.role || 'JUDGE', - imageUrl: person.avatar || null, - description: person.bio || null, + imageUrl: person.avatar || null, + description: person.bio || null, socialLinks: person.socialLinks || null })) }, @@ -161,7 +162,10 @@ export const getEvents = async (req, res) => { applicationForm: true, applications: { include: { - team: true // Include team details in response + team: true, + user: { + select: { username: true } + } } }, customQuestions: true, @@ -188,7 +192,7 @@ export const getEvents = async (req, res) => { export const getEventById = async (req, res) => { try { const { id } = req.params; - + const event = await prisma.event.findUnique({ where: { id: parseInt(id) }, include: { @@ -206,7 +210,7 @@ export const getEventById = async (req, res) => { applicationForm: true, applications: { include: { - team: true // Include team details in response + team: true } }, createdBy: { @@ -235,8 +239,8 @@ export const getCustomQuestions = async (req, res) => { const { eventId } = req.params; // Extract eventId from URL const questions = await prisma.customQuestion.findMany({ - where: { eventId: parseInt(eventId) }, // Filter by eventId - select: { + where: { eventId: parseInt(eventId) }, + select: { questionText: true, questionType: true, options: true, @@ -244,7 +248,7 @@ export const getCustomQuestions = async (req, res) => { } }); - res.json(questions); // Return questions as JSON + res.json(questions); } catch (error) { console.error("Error fetching questions:", error); res.status(500).json({ error: "Failed to fetch questions" }); @@ -253,9 +257,8 @@ export const getCustomQuestions = async (req, res) => { export const getAutoSave = async (req, res) => { try { - const userId = req.user.id; - + const event = await prisma.event.findFirst({ where: { AND: [ @@ -372,7 +375,7 @@ export const updateEvent = async (req, res) => { minTeamSize: updateData.minTeamSize, maxTeamSize: updateData.maxTeamSize, mode: updateData.mode, - status: updateData.status || event.status, // Keep existing status if not specified + status: updateData.status || event.status, timeline: { update: { @@ -521,9 +524,9 @@ export const deleteEvent = async (req, res) => { export const joinEvent = async (req, res) => { // ... existing joinEvent code ... + // If you want me to paste the full joinEvent implementation here, tell me and I'll include it. }; -// Add the missing applyToEvent function export const applyToEvent = async (req, res) => { try { console.log("Received request body:", req.body); @@ -544,109 +547,102 @@ export const applyToEvent = async (req, res) => { let existingTeam; // Check if the team already exists using the hashCode - if(mode === 'create'){ - existingTeam = await prisma.team.findUnique({ - where: { + if (mode === 'create') { + existingTeam = await prisma.team.findUnique({ + where: { hashCode: team.hash } - }); - - if (!existingTeam) { - // If the team doesn't exist, create it - existingTeam = await prisma.team.create({ - data: { - eventId: parseInt(eventId), - name: team.name, - hashCode: team.hash, - } - }); - } else { - return res.status(500).json({ error: 'Generate a new hash.' }); //Incase hash is already in use - } - + }); - // Add the user as a member of the team if not already a member - const existingTeamMember = await prisma.teamMember.findFirst({ - where: { - teamId: existingTeam.id, - userId: user.id + if (!existingTeam) { + // If the team doesn't exist, create it + existingTeam = await prisma.team.create({ + data: { + eventId: parseInt(eventId), + name: team.name, + hashCode: team.hash, } }); + } else { + return res.status(500).json({ error: 'Generate a new hash.' }); //Incase hash is already in use + } - if (!existingTeamMember) { - await prisma.teamMember.create({ - data: { - teamId: existingTeam.id, - userId: user.id, - role: 'LEADER' // Set default role, change as needed - } - }); - } else { - return res.status(500).json({ error: 'Already in the team' }); - } - } else if (mode === 'join'){ - existingTeam = await prisma.team.findUnique({ - where: { - hashCode: team.hash, - eventId: parseInt(eventId) - } - }); - - if (!existingTeam) { - // If the team user is trying to join does not exist, send error - return res.status(500).json({ error: 'Team does not exist' }); + // Add the user as a member of the team if not already a member + const existingTeamMember = await prisma.teamMember.findFirst({ + where: { + teamId: existingTeam.id, + userId: user.id } + }); - let presentInTeam = await prisma.teamMember.count({ - where : { + if (!existingTeamMember) { + await prisma.teamMember.create({ + data: { teamId: existingTeam.id, - userId: user.id + userId: user.id, + role: 'LEADER' } }); - - //If user is already part of the team - if(presentInTeam > 0){ - return res.status(500).json({ error: 'Registrant is already a part of the team.' }); + } else { + return res.status(500).json({ error: 'Already in the team' }); + } + } else if (mode === 'join') { + existingTeam = await prisma.team.findUnique({ + where: { + hashCode: team.hash, + eventId: parseInt(eventId) } + }); - let maxTeamSize = await prisma.event.findUnique({ - where : {id : parseInt(eventId)}, - select: { maxTeamSize: true } - }); - - let currentTeamSize = await prisma.teamMember.count({ - where : { teamId: existingTeam.id} - }); + if (!existingTeam) { + return res.status(500).json({ error: 'Team does not exist' }); + } - //to check for maximum allowed participants in a team - if(currentTeamSize === maxTeamSize.maxTeamSize){ - return res.status(500).json({ error: 'Team is at maximum capacity' }); + let presentInTeam = await prisma.teamMember.count({ + where: { + teamId: existingTeam.id, + userId: user.id } + }); - await prisma.teamMember.create({ - data: { - teamId: existingTeam.id, - userId: user.id, - role: 'MEMBER' // Set default role, change as needed - } - }); + if (presentInTeam > 0) { + return res.status(500).json({ error: 'Registrant is already a part of the team.' }); + } - } + let maxTeamSize = await prisma.event.findUnique({ + where: { id: parseInt(eventId) }, + select: { maxTeamSize: true } + }); + let currentTeamSize = await prisma.teamMember.count({ + where: { teamId: existingTeam.id } + }); + if (currentTeamSize === maxTeamSize.maxTeamSize) { + return res.status(500).json({ error: 'Team is at maximum capacity' }); + } + + await prisma.teamMember.create({ + data: { + teamId: existingTeam.id, + userId: user.id, + role: 'MEMBER' + } + }); + } // Create application with team reference const application = await prisma.application.create({ data: { eventId: parseInt(eventId), userId: user.id, - teamId: existingTeam.id, // Link application to team + teamId: existingTeam.id, status: 'PENDING', userData: userData, responses: responses || {} }, include: { - team: true // Include team details in response + team: true } }); @@ -657,7 +653,6 @@ export const applyToEvent = async (req, res) => { } }; - export const publishEvent = async (req, res) => { try { const { id } = req.params; @@ -720,7 +715,7 @@ export const publishEvent = async (req, res) => { export const getApplication = async (req, res) => { try { // Extract user ID from Auth0 token - const auth0Id = req.auth.payload.sub; + const auth0Id = req.auth.payload.sub; const eventId = parseInt(req.params.eventId, 10); if (!auth0Id) { @@ -733,18 +728,17 @@ export const getApplication = async (req, res) => { const user = await prisma.user.findUnique({ where: { auth0Id }, - select: { id: true }, // Only fetch the user ID + select: { id: true }, }); if (!user) { return res.status(404).json({ error: "User not found" }); } - // Fetch the specific application related to the user and event const application = await prisma.application.findFirst({ where: { - userId: user.id, - eventId, + userId: user.id, + eventId, }, include: { event: true, @@ -752,7 +746,6 @@ export const getApplication = async (req, res) => { }, }); - if (!application) { return res.status(200).json({ error: "Application not found for this user and event" }); } @@ -762,7 +755,7 @@ export const getApplication = async (req, res) => { console.error("Error fetching application:", error); res.status(500).json({ error: "Internal Server Error" }); } -} +}; export const updateApplication = async (req, res) => { try { @@ -778,7 +771,7 @@ export const updateApplication = async (req, res) => { if (isNaN(eventId)) { return res.status(400).json({ error: "Invalid event ID" }); } - + // Find the application belonging to the user and event const application = await prisma.application.findFirst({ where: { @@ -787,7 +780,7 @@ export const updateApplication = async (req, res) => { }, }); - console.log(application) + console.log(application); if (!application) { return res.status(404).json({ error: "Application not found" }); @@ -803,12 +796,55 @@ export const updateApplication = async (req, res) => { }, }); + // 🔔 Trigger notification if status changed to ACCEPTED or REJECTED + if (status === "ACCEPTED") { + try { + await createNotification({ + userId: updatedApplication.userId, + title: "Application Approved 🎉", + message: "Congratulations! Your application has been accepted.", + type: "APPLICATION", + }); + } catch (notifErr) { + console.error("Failed to create ACCEPTED notification:", notifErr); + // don't fail the request if notification fails + } + } + + if (status === "REJECTED") { + try { + await createNotification({ + userId: updatedApplication.userId, + title: "Application Update", + message: "Your application was not selected this time.", + type: "APPLICATION", + }); + } catch (notifErr) { + console.error("Failed to create REJECTED notification:", notifErr); + } + } + res.status(200).json(updatedApplication); } catch (error) { console.error("Error updating application:", error); res.status(500).json({ error: "Internal Server Error" }); } }; +// PATCH /api/events/:id/attendance +export const updateAttendance = async (req, res) => { + const { applicationId, attendance } = req.body; + + try { + const updated = await prisma.application.update({ + where: { id: applicationId }, + data: { attendance } + }); + res.json(updated); + } catch (err) { + console.error('Error updating attendance:', err); + res.status(500).json({ error: 'Failed to update attendance' }); + } +}; // export const deleteApplication = async (req, res) => { // try { @@ -856,4 +892,4 @@ export const updateApplication = async (req, res) => { // console.error("Error deleting application:", error); // res.status(500).json({ error: "Internal Server Error" }); // } -// }; +// }; \ No newline at end of file diff --git a/backend/src/controllers/friends.js b/backend/src/controllers/friends.js new file mode 100644 index 0000000..776d169 --- /dev/null +++ b/backend/src/controllers/friends.js @@ -0,0 +1,159 @@ +import prisma from '../config/database.js'; + +export const sendFriendRequest = async (req, res) => { + try { + const senderAuth0Id = req.auth.payload.sub; + const { receiverId } = req.body; // This is the user ID of the receiver + + const sender = await prisma.user.findUnique({ where: { auth0Id: senderAuth0Id } }); + + // Prevent duplicate requests + const existing = await prisma.friendRequest.findFirst({ + where: { + OR: [ + { senderId: sender.id, receiverId }, + { senderId: receiverId, receiverId: sender.id } + ] + } + }); + + if (existing) { + return res.status(400).json({ error: 'Request already exists or you are already friends.' }); + } + + const request = await prisma.friendRequest.create({ + data: { + senderId: sender.id, + receiverId + } + }); + + res.status(201).json(request); + } catch (error) { + console.error('Send friend request error:', error); + res.status(500).json({ error: 'Failed to send request' }); + } +}; + +export const respondToRequest = async (req, res) => { + try { + const { requestId } = req.params; + const { action } = req.body; // 'ACCEPT' or 'REJECT' + const userAuth0Id = req.auth.payload.sub; + + const user = await prisma.user.findUnique({ where: { auth0Id: userAuth0Id } }); + + const request = await prisma.friendRequest.findUnique({ + where: { id: requestId } + }); + + if (!request || request.receiverId !== user.id) { + return res.status(403).json({ error: 'Not authorized' }); + } + + if (action === 'ACCEPT') { + await prisma.$transaction([ + prisma.friendRequest.update({ + where: { id: requestId }, + data: { status: 'ACCEPTED' } + }), + prisma.friendship.create({ + data: { userId: request.senderId, friendId: request.receiverId } + }), + prisma.friendship.create({ + data: { userId: request.receiverId, friendId: request.senderId } + }) + ]); + } else { + await prisma.friendRequest.update({ + where: { id: requestId }, + data: { status: 'REJECTED' } + }); + } + + res.json({ success: true, action }); + } catch (error) { + console.error('Respond to request error:', error); + res.status(500).json({ error: 'Failed to respond to request' }); + } +}; + +export const getFriends = async (req, res) => { + try { + const userAuth0Id = req.auth.payload.sub; + const user = await prisma.user.findUnique({ where: { auth0Id: userAuth0Id } }); + + const friendships = await prisma.friendship.findMany({ + where: { userId: user.id }, + include: { + friend: { include: { profile: true } } + } + }); + + res.json(friendships.map(f => f.friend)); + } catch (error) { + console.error('Get friends error:', error); + res.status(500).json({ error: 'Failed to fetch friends' }); + } +}; + +export const followOrganizer = async (req, res) => { + // Simplified follow logic + try { + const followerAuth0Id = req.auth.payload.sub; + const { organizerId } = req.body; + + const follower = await prisma.user.findUnique({ where: { auth0Id: followerAuth0Id } }); + + const follow = await prisma.follow.create({ + data: { + followerId: follower.id, + followingId: organizerId + } + }); + + res.status(201).json(follow); + } catch (error) { + console.error('Follow error:', error); + res.status(500).json({ error: 'Failed to follow' }); + } +}; + +export const getPendingRequests = async (req, res) => { + try { + const userAuth0Id = req.auth.payload.sub; + const user = await prisma.user.findUnique({ where: { auth0Id: userAuth0Id } }); + + const requests = await prisma.friendRequest.findMany({ + where: { receiverId: user.id, status: 'PENDING' }, + include: { + sender: { include: { profile: true } } + } + }); + + res.json(requests); + } catch (error) { + console.error('Get pending requests error:', error); + res.status(500).json({ error: 'Failed to fetch requests' }); + } +}; + +export const searchUsers = async (req, res) => { + try { + const { query } = req.query; + if (!query) return res.json([]); + + const users = await prisma.user.findMany({ + where: { + username: { contains: query, mode: 'insensitive' } + }, + take: 10, + include: { profile: true } + }); + + res.json(users); + } catch (error) { + console.error('Search users error:', error); + res.status(500).json({ error: 'Failed to search users' }); + } +}; diff --git a/backend/src/controllers/messages.js b/backend/src/controllers/messages.js new file mode 100644 index 0000000..c132227 --- /dev/null +++ b/backend/src/controllers/messages.js @@ -0,0 +1,128 @@ +import prisma from '../config/database.js'; + +export const getInbox = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const user = await prisma.user.findUnique({ where: { auth0Id } }); + + const conversations = await prisma.conversation.findMany({ + where: { + members: { + some: { userId: user.id } + } + }, + include: { + members: { + include: { user: { include: { profile: true } } } + }, + messages: { + orderBy: { createdAt: 'desc' }, + take: 1 + } + }, + orderBy: { updatedAt: 'desc' } + }); + + res.json(conversations); + } catch (error) { + console.error('Get inbox error:', error); + res.status(500).json({ error: 'Failed to fetch messages' }); + } +}; + +export const getConversation = async (req, res) => { + try { + const { conversationId } = req.params; + const auth0Id = req.auth.payload.sub; + + const messages = await prisma.message.findMany({ + where: { conversationId }, + orderBy: { createdAt: 'asc' }, + include: { + sender: { include: { profile: true } } + } + }); + + // Mark as read + await prisma.message.updateMany({ + where: { + conversationId, + sender: { auth0Id: { not: auth0Id } }, + isRead: false + }, + data: { isRead: true } + }); + + res.json(messages); + } catch (error) { + console.error('Get conversation messages error:', error); + res.status(500).json({ error: 'Failed to fetch conversation' }); + } +}; + +export const sendMessage = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const { conversationId, receiverId, content } = req.body; + + const sender = await prisma.user.findUnique({ where: { auth0Id } }); + let convId = conversationId; + + // If starting a new direct message thread + if (!convId && receiverId) { + // Find existing + const existing = await prisma.conversation.findFirst({ + where: { + isGroup: false, + AND: [ + { members: { some: { userId: sender.id } } }, + { members: { some: { userId: receiverId } } } + ] + } + }); + + if (existing) { + convId = existing.id; + } else { + const newConv = await prisma.conversation.create({ + data: { + members: { + create: [ + { userId: sender.id }, + { userId: receiverId } + ] + } + } + }); + convId = newConv.id; + } + } + + const message = await prisma.message.create({ + data: { + conversationId: convId, + senderId: sender.id, + content + }, + include: { + sender: { include: { profile: true } } + } + }); + + await prisma.conversation.update({ + where: { id: convId }, + data: { updatedAt: new Date() } + }); + + // Emit socket event if io is accessible + const io = req.app.get('socketio'); + if (io) { + io.to(`conversation_${convId}`).emit('message:receive', message); + } + + res.status(201).json(message); + } catch (error) { + console.error('Send message error:', error); + res.status(500).json({ error: 'Failed to send message' }); + } +}; diff --git a/backend/src/controllers/posts.js b/backend/src/controllers/posts.js new file mode 100644 index 0000000..9e6fc29 --- /dev/null +++ b/backend/src/controllers/posts.js @@ -0,0 +1,193 @@ +import prisma from '../config/database.js'; + +export const createPost = async (req, res) => { + try { + const userId = req.auth.payload.sub; + const { content, eventId, tags, images } = req.body; + + const post = await prisma.post.create({ + data: { + content, + author: { connect: { auth0Id: userId } }, + ...(eventId && { event: { connect: { id: eventId } } }), + ...(images && images.length > 0 && { + images: { + create: images.map(url => ({ url })) + } + }), + ...(tags && tags.length > 0 && { + tags: { + create: tags.map(tag => ({ + tag: { + connectOrCreate: { + where: { name: tag }, + create: { name: tag } + } + } + })) + } + }) + }, + include: { + author: { include: { profile: true } }, + images: true, + tags: { include: { tag: true } }, + event: true, + reactions: true, + _count: { select: { postcomment: true, reactions: true } } + } + }); + + res.status(201).json(post); + } catch (error) { + console.error('Create post error:', error); + res.status(500).json({ error: 'Failed to create post' }); + } +}; + +export const getPosts = async (req, res) => { + try { + const page = parseInt(req.query.page) || 1; + const limit = parseInt(req.query.limit) || 10; + const skip = (page - 1) * limit; + + const posts = await prisma.post.findMany({ + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + include: { + author: { include: { profile: true } }, + images: true, + tags: { include: { tag: true } }, + event: true, + reactions: true, + _count: { select: { postcomment: true } } + } + }); + + res.json(posts); + } catch (error) { + console.error('Get posts error:', error); + res.status(500).json({ error: 'Failed to fetch posts' }); + } +}; + +export const reactToPost = async (req, res) => { + try { + const userId = req.auth.payload.sub; + const { id: postId } = req.params; + const { type } = req.body; // LIKE, CELEBRATE, SUPPORT + + const user = await prisma.user.findUnique({ where: { auth0Id: userId } }); + + // Check existing reaction + const existing = await prisma.reaction.findUnique({ + where: { postId_userId: { postId, userId: user.id } } + }); + + if (existing) { + if (existing.type === type) { + // Toggle off + await prisma.reaction.delete({ where: { id: existing.id } }); + return res.json({ message: 'Reaction removed' }); + } else { + // Update type + const updated = await prisma.reaction.update({ + where: { id: existing.id }, + data: { type } + }); + return res.json(updated); + } + } + + const reaction = await prisma.reaction.create({ + data: { + type, + post: { connect: { id: postId } }, + user: { connect: { id: user.id } } + } + }); + + res.json(reaction); + } catch (error) { + console.error('Reaction error:', error); + res.status(500).json({ error: 'Failed to add reaction' }); + } +}; + +export const addComment = async (req, res) => { + try { + const auth0Id = req.auth.payload.sub; + const { id: postId } = req.params; + const { content, parentCommentId } = req.body; + + const comment = await prisma.PostComment.create({ + data: { + content, + post: { connect: { id: postId } }, + user: { connect: { auth0Id } }, + ...(parentCommentId && { parentComment: { connect: { id: parentCommentId } } }) + }, + include: { + user: { include: { profile: true } } + } + }); + + res.status(201).json(comment); + } catch (error) { + console.error('Add comment error:', error); + res.status(500).json({ error: 'Failed to add comment' }); + } +}; + +export const getComments = async (req, res) => { + try { + const { id: postId } = req.params; + + // Fetch top level comments and their replies (1 level deep) + const comments = await prisma.PostComment.findMany({ + where: { postId, parentCommentId: null }, + orderBy: { createdAt: 'asc' }, + include: { + user: { include: { profile: true } }, + replies: { + include: { user: { include: { profile: true } } }, + orderBy: { createdAt: 'asc' } + } + } + }); + + res.json(comments); + } catch (error) { + console.error('Get comments error:', error); + res.status(500).json({ error: 'Failed to fetch comments' }); + } +}; + +export const getUserProfile = async (req, res) => { + try { + const { id } = req.params; + const user = await prisma.user.findUnique({ + where: { id }, + include: { + profile: true, + education: true, + experience: true, + skills: true, + socialProfiles: true, + projects: true + } + }); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + // Don't send sensitive info like auth0Id + const { auth0Id, ...publicUser } = user; + res.json(publicUser); + } catch (error) { + console.error('Get user profile error:', error); + res.status(500).json({ error: 'Failed to fetch user profile' }); + } +}; diff --git a/backend/src/middleware/error.js b/backend/src/middleware/error.js index 728768a..e2b24ea 100644 --- a/backend/src/middleware/error.js +++ b/backend/src/middleware/error.js @@ -1,5 +1,5 @@ const errorHandler = (err, req, res, next) => { - console.error(err.stack); + // console.error(err.stack); if (err.name === 'PrismaClientKnownRequestError') { return res.status(400).json({ @@ -8,6 +8,13 @@ const errorHandler = (err, req, res, next) => { }); } + if (err.name === 'UnauthorizedError') { + return res.status(401).json({ + error: 'Unauthorized', + details: err.message + }); + } + if (err.name === 'ValidationError') { return res.status(400).json({ error: 'Validation failed', diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index aaa3d9f..32626e2 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -9,6 +9,7 @@ router.post('/register', register); // Protected routes router.get('/user', checkJwt, getCurrentUser); +router.get('/me', checkJwt, getCurrentUser); router.put('/user', checkJwt, updateUser); export default router; \ No newline at end of file diff --git a/backend/src/routes/clubs.js b/backend/src/routes/clubs.js new file mode 100644 index 0000000..fb2c791 --- /dev/null +++ b/backend/src/routes/clubs.js @@ -0,0 +1,30 @@ +import express from 'express'; +import { checkJwt, ensureUser } from '../middleware/auth.js'; +import { + getClubs, + getClubById, + createProject, + updateProject, + deleteProject, + getComments, + createComment, + deleteComment +} from '../controllers/clubs.js'; + +const router = express.Router(); + +// ─── Public routes ──────────────────────────────────────────────────────────── +router.get('/', getClubs); +router.get('/:id', getClubById); +router.get('/:projectId/comments', getComments); + +// ─── Protected routes (logged in) ───────────────────────────────────────────── +router.post('/:projectId/comments', checkJwt, ensureUser, createComment); +router.delete('/comments/:id', checkJwt, ensureUser, deleteComment); + +// ─── Convener only routes ────────────────────────────────────────────────────── +router.post('/:id/projects', checkJwt, ensureUser, createProject); +router.put('/projects/:id', checkJwt, ensureUser, updateProject); +router.delete('/projects/:id', checkJwt, ensureUser, deleteProject); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/events.js b/backend/src/routes/events.js index ce5f8d0..35d58d0 100644 --- a/backend/src/routes/events.js +++ b/backend/src/routes/events.js @@ -12,23 +12,21 @@ import { publishEvent, getAutoSave, getApplication, - updateApplication + updateApplication, + updateAttendance } from '../controllers/events.js'; -import { validate, eventSchema , eventDraftSchema} from '../middleware/validate.js'; +import { validate, eventSchema, eventDraftSchema } from '../middleware/validate.js'; const router = express.Router(); // Protected Organizer only route router.get('/autosave', checkJwt, ensureUser, isOrganizer, getAutoSave); - // Public routes router.get('/', getEvents); router.get('/:id', getEventById); router.get('/:eventId/custom-questions', getCustomQuestions); - - // Protected routes router.use(checkJwt); router.use(ensureUser); @@ -39,14 +37,15 @@ router.post('/draft', isOrganizer, validate(eventDraftSchema), createEvent); router.put('/:id', isOrganizer, updateEvent); router.delete('/:id', isOrganizer, deleteEvent); - -// Participant routes (require complete profile) +// Participant routes router.post('/:id/apply', checkJwt, ensureUser, requireCompleteProfile, applyToEvent); router.post('/:id/join', joinEvent); // Application routes -router.get('/:eventId/application', checkJwt, ensureUser, getApplication) -//router.delete('/application/:id', deleteApplication) -router.put('/:eventId/application', checkJwt, ensureUser, updateApplication) +router.get('/:eventId/application', checkJwt, ensureUser, getApplication); +router.put('/:eventId/application', checkJwt, ensureUser, updateApplication); + +// Attendance route +router.patch('/:id/attendance', updateAttendance); -export default router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/backend/src/routes/friends.js b/backend/src/routes/friends.js new file mode 100644 index 0000000..37a497d --- /dev/null +++ b/backend/src/routes/friends.js @@ -0,0 +1,17 @@ +import express from 'express'; +import { checkJwt, ensureUser } from '../middleware/auth.js'; +import { sendFriendRequest, respondToRequest, getFriends, followOrganizer, getPendingRequests, searchUsers } from '../controllers/friends.js'; + +const router = express.Router(); + +router.use(checkJwt); +router.use(ensureUser); + +router.post('/request', sendFriendRequest); +router.post('/request/:requestId', respondToRequest); // action in body +router.get('/requests/pending', getPendingRequests); +router.get('/search', searchUsers); +router.get('/', getFriends); +router.post('/follow', followOrganizer); + +export default router; diff --git a/backend/src/routes/index.js b/backend/src/routes/index.js index 2e950ce..070e2b0 100644 --- a/backend/src/routes/index.js +++ b/backend/src/routes/index.js @@ -1,11 +1,13 @@ import express from 'express'; import authRoutes from './auth.js'; import eventRoutes from './events.js'; +import clubRoutes from './clubs.js'; import { checkJwt } from '../middleware/auth.js'; const router = express.Router(); router.use('/auth', authRoutes); router.use('/events', checkJwt, eventRoutes); // Protected route +router.use('/clubs', clubRoutes); export default router; \ No newline at end of file diff --git a/backend/src/routes/messages.js b/backend/src/routes/messages.js new file mode 100644 index 0000000..b66d0a4 --- /dev/null +++ b/backend/src/routes/messages.js @@ -0,0 +1,14 @@ +import express from 'express'; +import { checkJwt, ensureUser } from '../middleware/auth.js'; +import { getInbox, getConversation, sendMessage } from '../controllers/messages.js'; + +const router = express.Router(); + +router.use(checkJwt); +router.use(ensureUser); + +router.get('/inbox', getInbox); +router.get('/:conversationId', getConversation); +router.post('/', sendMessage); + +export default router; diff --git a/backend/src/routes/posts.js b/backend/src/routes/posts.js new file mode 100644 index 0000000..dc33643 --- /dev/null +++ b/backend/src/routes/posts.js @@ -0,0 +1,17 @@ +import express from 'express'; +import { checkJwt, ensureUser } from '../middleware/auth.js'; +import { createPost, getPosts, reactToPost, addComment, getComments, getUserProfile } from '../controllers/posts.js'; + +const router = express.Router(); + +router.use(checkJwt); +router.use(ensureUser); + +router.post('/', createPost); +router.get('/', getPosts); +router.post('/:id/react', reactToPost); +router.post('/:id/comment', addComment); +router.get('/:id/comments', getComments); +router.get('/user/:id', getUserProfile); + +export default router; diff --git a/backend/src/server.js b/backend/src/server.js index cabcd71..1d55d7a 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -8,7 +8,21 @@ import { errorHandler } from './middleware/error.js'; const app = express(); // Security middleware -app.use(helmet()); +app.use( + helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + connectSrc: [ + "'self'", + "http://localhost:4000", + "https://*.supabase.co", + "https://wec-auth.us.auth0.com", + ], + }, + }, + }) +); app.use(cors({ origin: process.env.FRONTEND_URL || 'http://localhost:3000', credentials: true, diff --git a/backend/src/utils/notification.js b/backend/src/utils/notification.js new file mode 100644 index 0000000..b24dcf9 --- /dev/null +++ b/backend/src/utils/notification.js @@ -0,0 +1,21 @@ +// import prisma from "../prisma/client.js"; + +// export const createNotification = async ({ +// userId, +// title, +// message, +// type, +// }) => { +// try { +// await prisma.notification.create({ +// data: { +// userId, +// title, +// message, +// type, +// }, +// }); +// } catch (error) { +// console.error("Notification creation error:", error); +// } +// }; \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 238d2e4..11e8c8c 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -29,6 +29,9 @@ export default [ ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, 'react/jsx-no-target-blank': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + 'no-unused-vars': ['warn', { 'argsIgnorePattern': '^_' }], 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, diff --git a/frontend/index.html b/frontend/index.html index 6d1c806..2b4a7f3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@
- +