diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..83db769 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +This document outlines the GitHub standards and workflow for all team members. Following these rules is mandatory to maintain code quality and a clean repository. +# 1. Branching Convention +All new work (features, bug fixes) **must** be done on a separate branch. Do not commit directly to main or dev. +### Branch Naming +Branches must be named using the following prefixes, followed by a short, hyphenated description: +- feat/: For new features (e.g., feat/add-login-page) +- fix/: For bug fixes (e.g., fix/login-delay-bug) +- docs/: For documentation changes (e.g., docs/update-architecture-diagram) +- style/: For code styling changes (e.g., style/format-all-files) +- refactor/: For code refactoring without changing functionality (e.g., refactor/simplify-report-logic) +**Base Branch:** All feature and fix branches should be created from the dev branch. +# 2. Commit Message Template +We follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. This makes our commit history readable and helps automate versioning. +Your commit message **must** follow this format: +`: [optional body]` +`` **must be one of the following:** +- feat: A new feature +- fix: A bug fix +- docs: Documentation only changes +- style: Changes that do not affect the meaning of the code (white-space, formatting, etc) +- refactor: A code change that neither fixes a bug nor adds a feature +- perf: A code change that improves performance +- test: Adding missing tests or correcting existing tests +**Examples:** +- feat: add rubric upload button to assignment page +- fix: correct calculation for mark deviation +- docs: update CONTRIBUTING.md with new commit types +- refactor: simplify user authentication logic +# 3. GitHub Workflow: The Pull Request (PR) Process +Committing directly to dev or main is **prohibited**. All code must be submitted via a Pull Request (PR). +1. **Create Your Branch:** Create your feat/ or fix/ branch from the latest dev branch. +2. **Do Your Work:** Write your code and commit your changes using the commit message template (see section 2). +3. **Perform Self-Test:** Before creating a PR, you **must** self-test your changes locally to ensure they work. (This is from our QA Standard). +4. **Create Pull Request:** + - Push your branch to GitHub. + - Create a Pull Request from your branch into the dev branch. + - **PR Title:** Use a clear title (e.g., feat: Add Rubric Upload Feature). + - **PR Description:** + - Briefly describe _what_ you changed and _why_. + - **Link the Issue:** You **must** link the GitHub Issue this PR resolves (e.g., Closes #25). + - **Confirm Self-Test:** You **must** confirm in writing that you have self-tested (e.g., [x] Tested locally, feature works as expected.). +5. **Request Review:** + - Assign at least **one (1) other team member** as a **Reviewer**. + - Do **not** merge your own PR. +6. **Code Review:** + - The Reviewer will check the code for correctness, style, and adherence to the QA Standard. + - The Reviewer may approve the PR or request changes. +7. **Merge:** + - Once the PR is **approved** by the reviewer and all automated checks (if any) have passed, the PR can be merged into dev. \ No newline at end of file diff --git a/QA (Quality Assurance) Standards.md b/QA (Quality Assurance) Standards.md new file mode 100644 index 0000000..437d737 --- /dev/null +++ b/QA (Quality Assurance) Standards.md @@ -0,0 +1,35 @@ +## 1. Purpose +This document defines the quality assurance process and standards our team follows to ensure all features are high-quality, bug-free, and meet client requirements. +## 2. Definition of Done (DoD) +A User Story or Task is considered **"Done"** only when it meets **all** of the following criteria: +- [ ] Code is written and **merged** to the dev branch. +- [ ] Code adheres to the project's coding standards. +- [ ] Code is **peer-reviewed** and **approved** by at least one other team member. +- [ ] All related test cases in the TestPlan.md have been **passed**. +- [ ] The feature has been deployed to the test environment and verified by QA (Qi Lin). +- [ ] The feature meets all acceptance criteria defined in the User Story. +- [ ] No new high-priority bugs are introduced. +## 3. Roles and Responsibilities +### Developers (Eric, Yihan, Baitong, Ke, Qi) +- **Must** write code that is clean and functional. +- **Must** perform developer self-testing (smoke testing) locally before creating a Pull Request. +- **Must** participate in code reviews for other team members. +### QA (Qi Lin) +- **Responsible** for creating and maintaining the TestPlan.md. +- **Responsible** for participating in Sprint Planning to identify testing needs early. +- **Responsible** for performing formal testing in the test environment. +- **Responsible** for logging all bugs found in GitHub Issues. +- **Responsible** for final verification that a feature meets the "Definition of Done". +## 4. Code Review Policy +- All new features **must** be developed on a separate branch (e.g., feat/feature-name). +- All code **must** be submitted via a Pull Request (PR) to the dev branch. +- All PRs **must** be approved by at least **one (1)** other team member before merging. +- The PR description **must** include proof of developer self-testing. +## 5. Bug (Defect) Tracking Policy +- All bugs, no matter how small, **must** be reported as "Issues" in our GitHub repository. +- Bug reports **must** include: + - A clear title. + - Steps to reproduce the bug. + - Expected results vs. Actual results. + - A priority label (e.g., High, Medium, Low). +- High-priority bugs must be fixed before new features are developed. \ No newline at end of file diff --git a/README.md b/README.md index 5db5a63..9c0c576 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Here's how to install most of the stuff to get it working cd frontend-vite npm install # Installs the frontend dependencies npm install -D tailwindcss postcss autoprefixer # You might need this if tailwind doesn't install off npm - npm install axois react-router-dom # If backend is failing + npm install axios react-router-dom # If backend is failing ``` ```bash diff --git a/TestPlan.md b/TestPlan.md new file mode 100644 index 0000000..108de36 --- /dev/null +++ b/TestPlan.md @@ -0,0 +1,60 @@ +# **Test Plan: Marker Moderation & Feedback Tool** + +### **Document Control** + +| Version | Date | Editor | Changes / Comments | +| :---- | :---- | :---- | :---- | +| 1.0 | 2025-09-04 | Qi Lin | Initial draft of the test plan based on user stories. | +| 1.1 | 2025-09-05 | Qi Lin | Expanded test cases to provide full coverage of all user stories. | +| 1.2 | 2025-09-06 | Qi Lin | Reviewed and corrected all user story references for traceability. | +| 1.3 | 2025-09-06 | Qi Lin | Final version for Week 6 progress report submission. Cleaned up formatting. | + +Document Version: 1.3 +Date: 6th September 2025 + +### **1\. Introduction** + +This document outlines the testing strategy for the "Marker Moderation & Feedback Tool" project. The purpose of this plan is to ensure that all functional and non-functional requirements are met, the system is free of critical defects, and the final product provides a reliable and intuitive user experience for both the Unit Chair and the Markers. + +### **2\. Scope of Testing** + +This plan covers the testing of all features defined in the project's User Stories and Use Case documents. The scope includes: + +* Functionality for the **Unit Chair (Admin)** role. +* Functionality for the **Marker** role. +* Data processing and statistical calculations. +* The automated notification system. + +Testing will be performed on the web application deployed in a staging environment, using modern desktop browsers (Chrome, Firefox). Mobile browser testing is out of scope. + +### **3\. Levels of Testing** + +Our testing strategy will incorporate the following levels: + +* **Unit Testing:** Testing individual functions and components in isolation to ensure they work correctly (e.g., a function that calculates the percentage difference between two scores). +* **Integration Testing:** Testing the interaction between different components to ensure they work together as expected (e.g., ensuring that submitting marks via the frontend correctly updates the database on the backend). +* **System Testing (End-to-End):** Testing the complete, integrated application to verify that it meets all specified requirements. This involves simulating full user journeys from start to finish. +* **User Acceptance Testing (UAT):** The final phase of testing where the client (Carrie Ewin) will test the application to confirm it meets her needs and is ready for use. + +### **4\. Test Cases** + +The following table details the specific test cases derived from the project's user stories. + +| Test Case ID | User Story Ref. | Test Description | Steps to Reproduce | Expected Result | Actual Result | Status | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | +| **TC-A-001** | Story 1.1 | **Admin: Create a new moderation task** | 1\. Log in as Unit Chair. 2\. Navigate to "Tasks". 3\. Click "Create New Task". 4\. Fill in task name "Test Task 1" and year "2025". 5\. Click "Save". | A new task named "Test Task 1" should appear in the task list. The system should confirm "Task created successfully." | | Not Run | +| **TC-A-002** | Story 1.2 | **Admin: Upload a rubric to a task** | 1\. Log in as Unit Chair. 2\. Open "Test Task 1". 3\. Click "Upload Rubric". 4\. Select a valid Word document rubric file. 5\. Click "Upload". | The system should parse the rubric and display its criteria and scoring structure on the screen for confirmation. | | Not Run | +| **TC-A-003** | Story 1.3 | **Admin: Enter baseline marks and commentary** | 1\. Log in as Unit Chair. 2\. Open the rubric editor for "Test Task 1". 3\. Enter a valid score and a comment for each criterion. 4\. Click "Save Baseline". | The scores and comments should be saved successfully. When reopening the editor, the saved data should be present. | | Not Run | +| **TC-A-004** | Story 3.1 & 3.2 | **Admin: View dashboard statistics** | 1\. Log in as Unit Chair. 2\. Navigate to the dashboard for a task with multiple marker submissions. 3\. Observe the statistics panel. | The dashboard should correctly display the average, standard deviation, min, and max scores for each criterion and the overall score. | | Not Run | +| **TC-A-005** | Story 3.4 | **Admin: View marker completion status & improvement** | 1\. Log in as Unit Chair. 2\. Navigate to the task dashboard. 3\. Observe the list of assigned markers. | The list should clearly indicate which markers have "Completed" their submission and which are "Pending", and show comparison data if it's the second moderation task. | | Not Run | +| **TC-A-006** | Story 3.3 | **Admin: Flag markers with high variance** | 1\. Log in as Unit Chair. 2\. Navigate to the dashboard where one marker's score is \>5% different from the baseline. 3\. Observe the marker list. | The marker whose score deviates by more than 5% should be visually highlighted (e.g., with a red background or an icon). | | Not Run | +| **TC-A-007** | Story 5.1 | **Admin: Add a new marker to the system** | 1\. Log in as Unit Chair. 2\. Navigate to "User Management". 3\. Click "Add Marker". 4\. Enter a name and valid email. 5\. Click "Save". | A new marker account should be created and appear in the user list. An invitation email should be sent to the marker. | | Not Run | +| **TC-A-008** | Story 3.5 | **Admin: Access historical moderation data** | 1\. Log in as Unit Chair. 2\. Ensure a completed task from a previous year exists. 3\. Navigate to the Tasks list. 4\. Filter/search for the previous year's task. 5\. Click to open it. | The system should load and display the full dashboard for the historical task, including all scores and statistics from that period. | | Not Run | +| **TC-M-001** | Story 2.1 | **Marker: View assigned tasks on dashboard** | 1\. Log in as a Marker. 2\. Observe the main dashboard/home page. | The dashboard should display a list of all tasks currently assigned to this marker, showing their names and statuses (e.g., "Not Started", "Completed"). | | Not Run | +| **TC-M-002** | Story 2.2 | **Marker: Submit marks for a task** | 1\. Log in as a Marker. 2\. Open an assigned task. 3\. Enter valid numerical scores for all criteria. 4\. Click "Submit". | The system should save the marks and immediately redirect to the feedback page. A "Submission successful" message should appear. | | Not Run | +| **TC-M-003** | Story 2.3 | **Marker: View instant feedback after submission** | (Triggered by successful completion of TC-M-002) | The feedback page should display: the marker's scores, the Unit Chair's baseline scores, the calculated difference, and the Unit Chair's commentary. | | Not Run | +| **TC-N-001** | Story 4.1 | **Notification: Marker receives email on submission** | (Triggered by successful completion of TC-M-002) | The marker should receive an email within 2 minutes containing a summary of their submission and the feedback. | | Not Run | +| **TC-N-002** | Story 4.2 | **Notification: Marker receives email for a new task** | (Triggered after TC-A-001) | When a new moderation task is created and assigned, the marker should receive an email notification about the new task. | | Not Run | +| **TC-N-003** | Story 4.3 | **Notification: Admin receives reminder about inactive markers** | 1\. Create a task and assign it to a marker. 2\. Wait for 7 days without the marker submitting. 3\. Check the Unit Chair's email inbox. | The Unit Chair should receive an email notification listing the markers who have not yet completed the task. | | Not Run | +| **TC-N-004** | Story 4.2 | **Notification: Admin is notified of a submission** | (Triggered by successful completion of TC-M-002) | The Unit Chair should receive an email notification informing them that a specific marker has completed their submission. | | Not Run | + diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..55567b5 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,12 @@ +# Backend environment variables template for local development +# Copy this to .env and fill in your own local values + +DATABASE_URL=postgresql://username:password@localhost:5432/markingapp +FRONTEND_URL=http://localhost:5173 +PORT=5000 + +# AWS S3 (replace with your own dev credentials if needed) +AWS_ACCESS_KEY_ID=your_aws_access_key_id_here +AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key_here +AWS_S3_BUCKET_NAME=markingapp-control-papers +AWS_REGION=ap-southeast-2 diff --git a/backend/database.js b/backend/database.js index b17a013..08a19e8 100644 --- a/backend/database.js +++ b/backend/database.js @@ -2,21 +2,20 @@ const { Pool } = require("pg"); const dotenv = require("dotenv"); -dotenv.config(); // loads DATABASE_URL from .env +dotenv.config(); -// Create a pool using your DATABASE_URL from Render or local .env const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: { - rejectUnauthorized: false // allows self-signed certs (Render’s default) + rejectUnauthorized: false }, }); - -// Function to initialize tables async function initDB() { try { - // USERS + // --- CREATE NEW TABLES --- + + // USERS (Kept as is) await pool.query(` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, @@ -27,7 +26,7 @@ async function initDB() { ); `); - // TEAMS + // TEAMS (Kept as is) await pool.query(` CREATE TABLE IF NOT EXISTS teams ( id SERIAL PRIMARY KEY, @@ -38,117 +37,116 @@ async function initDB() { ); `); - // TEAM MEMBERS + // TEAM MEMBERS (Kept as is) await pool.query(` CREATE TABLE IF NOT EXISTS team_members ( id SERIAL PRIMARY KEY, - team_id INTEGER NOT NULL REFERENCES teams(id), - user_id INTEGER NOT NULL REFERENCES users(id), + team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, role TEXT NOT NULL DEFAULT 'tutor', joined_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(team_id, user_id) ); `); - + + // TEAM INVITES (Kept as is) + await pool.query(` + CREATE TABLE IF NOT EXISTS team_invites ( + id SERIAL PRIMARY KEY, + team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE, + inviter_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + invitee_email TEXT NOT NULL, + status TEXT DEFAULT 'pending', + token TEXT UNIQUE NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW() + ); + `); + // ASSIGNMENTS await pool.query(` CREATE TABLE IF NOT EXISTS assignments ( id SERIAL PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, + team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE, created_by INTEGER NOT NULL REFERENCES users(id), - team_id INTEGER REFERENCES teams(id), - created_at TIMESTAMPTZ DEFAULT NOW(), - due_date TIMESTAMPTZ + course_code TEXT NOT NULL, + course_name TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'Marking', -- 'Marking' or 'Completed' + semester INTEGER NOT NULL, + due_date TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW() ); `); - // Ensure due_date exists for older DBs - await pool.query(` - ALTER TABLE assignments - ADD COLUMN IF NOT EXISTS due_date TIMESTAMPTZ; - `); - console.log("✅ Checked assignments table: due_date column exists or was added."); - - // SUBMISSIONS + // ASSIGNMENT_MARKERS (Many-to-Many join table) await pool.query(` - CREATE TABLE IF NOT EXISTS submissions ( + CREATE TABLE IF NOT EXISTS assignment_markers ( id SERIAL PRIMARY KEY, - assignment_id INTEGER NOT NULL REFERENCES assignments(id), - student_identifier TEXT NOT NULL, - file_path TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() + assignment_id INTEGER NOT NULL REFERENCES assignments(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + completed BOOLEAN NOT NULL DEFAULT FALSE, + UNIQUE(assignment_id, user_id) ); `); - // RUBRICS + + + // RUBRIC_CRITERIA (Each row of the rubric) await pool.query(` - CREATE TABLE IF NOT EXISTS rubrics ( + CREATE TABLE IF NOT EXISTS rubric_criteria ( id SERIAL PRIMARY KEY, - assignment_id INTEGER NOT NULL REFERENCES assignments(id), - section_name TEXT NOT NULL, - description TEXT, - max_marks INTEGER NOT NULL + assignment_id INTEGER NOT NULL REFERENCES assignments(id) ON DELETE CASCADE, + criterion_description TEXT NOT NULL, + points NUMERIC(5, 2) NOT NULL, + deviation_threshold NUMERIC(5, 2) NOT NULL DEFAULT 0, + admin_comments TEXT ); `); - // RUBRIC TIERS (new table) + // RUBRIC_TIERS (The 5 rating levels for each criterion) await pool.query(` CREATE TABLE IF NOT EXISTS rubric_tiers ( id SERIAL PRIMARY KEY, - rubric_id INTEGER NOT NULL REFERENCES rubrics(id) ON DELETE CASCADE, + criterion_id INTEGER NOT NULL REFERENCES rubric_criteria(id) ON DELETE CASCADE, tier_name TEXT NOT NULL, - description TEXT, - marks INTEGER NOT NULL + description TEXT NOT NULL, + lower_bound NUMERIC(5, 2) NOT NULL, + upper_bound NUMERIC(5, 2) NOT NULL ); `); - console.log("✅ Rubric tiers table checked/created."); - - // MARKS + + // SUBMISSIONS (Includes flag for control papers) await pool.query(` - CREATE TABLE IF NOT EXISTS marks ( + CREATE TABLE IF NOT EXISTS submissions ( id SERIAL PRIMARY KEY, - submission_id INTEGER NOT NULL REFERENCES submissions(id), - rubric_id INTEGER NOT NULL REFERENCES rubrics(id), - tutor_id INTEGER NOT NULL REFERENCES users(id), - marks_awarded INTEGER NOT NULL, - comments TEXT, + assignment_id INTEGER NOT NULL REFERENCES assignments(id) ON DELETE CASCADE, + student_identifier TEXT NOT NULL, + file_path TEXT, + is_control_paper BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() ); `); - - // CONTROL MARKS + + // MARKS (Stores individual marks from tutors for control papers) await pool.query(` - CREATE TABLE IF NOT EXISTS control_marks ( - id SERIAL PRIMARY KEY, - submission_id INTEGER NOT NULL REFERENCES submissions(id), - rubric_id INTEGER NOT NULL REFERENCES rubrics(id), - official_marks INTEGER NOT NULL, - comments TEXT - ); - `); - - // TEAM INVITES - await pool.query(` - CREATE TABLE IF NOT EXISTS team_invites ( + CREATE TABLE IF NOT EXISTS marks ( id SERIAL PRIMARY KEY, - team_id INTEGER NOT NULL REFERENCES teams(id), - inviter_id INTEGER NOT NULL REFERENCES users(id), - invitee_email TEXT NOT NULL, - status TEXT DEFAULT 'pending', - token TEXT UNIQUE NOT NULL, + submission_id INTEGER NOT NULL REFERENCES submissions(id) ON DELETE CASCADE, + criterion_id INTEGER NOT NULL REFERENCES rubric_criteria(id) ON DELETE CASCADE, + tutor_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + marks_awarded NUMERIC(5, 2) NOT NULL, + comments TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); `); - console.log("PostgreSQL tables initialized."); + console.log("New database schema initialized successfully."); + } catch (err) { - console.error("Error initializing PostgreSQL tables:", err); + console.error("Error during database reset and initialization:", err); } } // Immediately initialize DB on startup initDB(); -module.exports = pool; - +module.exports = pool; \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 32c8400..4521303 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,50 +9,1695 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@aws-sdk/client-s3": "^3.890.0", + "aws-sdk": "^2.1692.0", "bcryptjs": "^3.0.2", "body-parser": "^2.2.0", "cors": "^2.8.5", + "docx-pdf": "^0.0.1", + "docx-to-pdf": "^1.1.0", "dotenv": "^17.2.1", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", + "multer": "^2.0.2", + "multer-s3": "^3.0.1", "nodemailer": "^7.0.5", "pg": "^8.16.3", - "sqlite3": "^5.1.7" + "resend": "^6.4.0", + "sqlite3": "^5.1.7", + "utils": "^0.2.2" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.890.0.tgz", + "integrity": "sha512-yByS+gXYe0KALEEGz9vqIapSKAJ/bGgevOMzPDHZfxQXP1DQxOnPQCbsCC7GDpYpy7Q2Wx54fgU5bqyMd7cfpA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.890.0", + "@aws-sdk/credential-provider-node": "3.890.0", + "@aws-sdk/middleware-bucket-endpoint": "3.890.0", + "@aws-sdk/middleware-expect-continue": "3.887.0", + "@aws-sdk/middleware-flexible-checksums": "3.890.0", + "@aws-sdk/middleware-host-header": "3.887.0", + "@aws-sdk/middleware-location-constraint": "3.887.0", + "@aws-sdk/middleware-logger": "3.887.0", + "@aws-sdk/middleware-recursion-detection": "3.887.0", + "@aws-sdk/middleware-sdk-s3": "3.890.0", + "@aws-sdk/middleware-ssec": "3.887.0", + "@aws-sdk/middleware-user-agent": "3.890.0", + "@aws-sdk/region-config-resolver": "3.890.0", + "@aws-sdk/signature-v4-multi-region": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.890.0", + "@aws-sdk/util-user-agent-browser": "3.887.0", + "@aws-sdk/util-user-agent-node": "3.890.0", + "@aws-sdk/xml-builder": "3.887.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.0", + "@smithy/eventstream-serde-browser": "^4.1.1", + "@smithy/eventstream-serde-config-resolver": "^4.2.1", + "@smithy/eventstream-serde-node": "^4.1.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-blob-browser": "^4.1.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/hash-stream-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/md5-js": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.2", + "@smithy/middleware-retry": "^4.2.2", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.2", + "@smithy/util-defaults-mode-node": "^4.1.2", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", + "@smithy/util-stream": "^4.3.1", + "@smithy/util-utf8": "^4.1.0", + "@smithy/util-waiter": "^4.1.1", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.890.0.tgz", + "integrity": "sha512-vefYNwh/K5V5YiJpFJfoMPNqsoiRTqD7ZnkvR0cjJdwhOIwFnSKN1vz0OMjySTQmVMcG4JKGVul82ou7ErtOhQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.890.0", + "@aws-sdk/middleware-host-header": "3.887.0", + "@aws-sdk/middleware-logger": "3.887.0", + "@aws-sdk/middleware-recursion-detection": "3.887.0", + "@aws-sdk/middleware-user-agent": "3.890.0", + "@aws-sdk/region-config-resolver": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.890.0", + "@aws-sdk/util-user-agent-browser": "3.887.0", + "@aws-sdk/util-user-agent-node": "3.890.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.2", + "@smithy/middleware-retry": "^4.2.2", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.2", + "@smithy/util-defaults-mode-node": "^4.1.2", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.890.0.tgz", + "integrity": "sha512-CT+yjhytHdyKvV3Nh/fqBjnZ8+UiQZVz4NMm4LrPATgVSOdfygXHqrWxrPTVgiBtuJWkotg06DF7+pTd5ekLBw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@aws-sdk/xml-builder": "3.887.0", + "@smithy/core": "^3.11.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.890.0.tgz", + "integrity": "sha512-BtsUa2y0Rs8phmB2ScZ5RuPqZVmxJJXjGfeiXctmLFTxTwoayIK1DdNzOWx6SRMPVc3s2RBGN4vO7T1TwN+ajA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.890.0.tgz", + "integrity": "sha512-0sru3LVwsuGYyzbD90EC/d5HnCZ9PL4O9BA2LYT6b9XceC005Oj86uzE47LXb+mDhTAt3T6ZO0+ZcVQe0DDi8w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.890.0.tgz", + "integrity": "sha512-Mxv7ByftHKH7dE6YXu9gQ6ODXwO1iSO32t8tBrZLS3g8K1knWADIqDFv3yErQtJ8hp27IDxbAbVH/1RQdSkmhA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/credential-provider-env": "3.890.0", + "@aws-sdk/credential-provider-http": "3.890.0", + "@aws-sdk/credential-provider-process": "3.890.0", + "@aws-sdk/credential-provider-sso": "3.890.0", + "@aws-sdk/credential-provider-web-identity": "3.890.0", + "@aws-sdk/nested-clients": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.890.0.tgz", + "integrity": "sha512-zbPz3mUtaBdch0KoH8/LouRDcYSzyT2ecyCOo5OAFVil7AxT1jvsn4vX78FlnSVpZ4mLuHY8pHTVGi235XiyBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.890.0", + "@aws-sdk/credential-provider-http": "3.890.0", + "@aws-sdk/credential-provider-ini": "3.890.0", + "@aws-sdk/credential-provider-process": "3.890.0", + "@aws-sdk/credential-provider-sso": "3.890.0", + "@aws-sdk/credential-provider-web-identity": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.890.0.tgz", + "integrity": "sha512-dWZ54TI1Q+UerF5YOqGiCzY+x2YfHsSQvkyM3T4QDNTJpb/zjiVv327VbSOULOlI7gHKWY/G3tMz0D9nWI7YbA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.890.0.tgz", + "integrity": "sha512-ajYCZ6f2+98w8zG/IXcQ+NhWYoI5qPUDovw+gMqMWX/jL1cmZ9PFAwj2Vyq9cbjum5RNWwPLArWytTCgJex4AQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.890.0", + "@aws-sdk/core": "3.890.0", + "@aws-sdk/token-providers": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.890.0.tgz", + "integrity": "sha512-qZ2Mx7BeYR1s0F/H6wePI0MAmkFswmBgrpgMCOt2S4b2IpQPnUa2JbxY3GwW2WqX3nV0KjPW08ctSLMmlq/tKA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/nested-clients": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.890.0.tgz", + "integrity": "sha512-WG2kpV5XBUkyT7GFd+uO0kNrLw/ZMEdVqhfnM040R3v04WHhxe3ufTfHd7MCCh4fgQ96jJOBxYD2q0PXFD70hg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.2", + "@smithy/smithy-client": "^4.6.2", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.890.0" + } + }, + "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "license": "MIT", - "optional": true + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/@aws-sdk/lib-storage/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.890.0.tgz", + "integrity": "sha512-X/td72r18uLsB1Hv70uK9cFzvc5Xyd8fde1FR7aU9COzw2ncNFgG2TJkxHBjdkby/T6SL5R4kY49KjVT3KHnzA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-arn-parser": "3.873.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.887.0.tgz", + "integrity": "sha512-AlrTZZScDTG9SYeT82BC5cK/6Q4N0miN5xqMW/pbBqP3fNXlsdJOWKB+EKD3V6DV41EV5GVKHKe/1065xKSQ4w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.890.0.tgz", + "integrity": "sha512-l2HHqI8qtwve1vXWE/cMzi0v53rSz6PNj4aas4K+OR8rvaS4O8OuVdcTC1vQB+0sFSjWNNRFtZnIqixah0XDxw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/is-array-buffer": "^4.1.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.887.0.tgz", + "integrity": "sha512-ulzqXv6NNqdu/kr0sgBYupWmahISHY+azpJidtK6ZwQIC+vBUk9NdZeqQpy7KVhIk2xd4+5Oq9rxapPwPI21CA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.887.0.tgz", + "integrity": "sha512-eU/9Cq4gg2sS32bOomxdx2YF43kb+o70pMhnEBBnVVeqzE8co78SO5FQdWfRTfhNJgTyQ6Vgosx//CNMPIfZPg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.887.0.tgz", + "integrity": "sha512-YbbgLI6jKp2qSoAcHnXrQ5jcuc5EYAmGLVFgMVdk8dfCfJLfGGSaOLxF4CXC7QYhO50s+mPPkhBYejCik02Kug==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.887.0.tgz", + "integrity": "sha512-tjrUXFtQnFLo+qwMveq5faxP5MQakoLArXtqieHphSqZTXm21wDJM73hgT4/PQQGTwgYjDKqnqsE1hvk0hcfDw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.890.0.tgz", + "integrity": "sha512-58P1lrE606zpp29xH9Keh3j2BWfa2ciGBtygJTpulRMlqPL3U1gFfU2g5nDYJbjKgRtCgNIBqfmtkL4eikCb9w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-arn-parser": "3.873.0", + "@smithy/core": "^3.11.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.887.0.tgz", + "integrity": "sha512-1ixZks0IDkdac1hjPe4vdLSuD9HznkhblCEb4T0wNyw3Ee1fdXg+MlcPWywzG5zkPXLcIrULUzJg/OSYfaDXcQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.890.0.tgz", + "integrity": "sha512-x4+gLrOFGN7PnfxCaQbs3QEF8bMQE4CVxcOp066UEJqr2Pn4yB12Q3O+YntOtESK5NcTxIh7JlhGss95EHzNng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.890.0", + "@smithy/core": "^3.11.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.890.0.tgz", + "integrity": "sha512-D5qVNd+qlqdL8duJShzffAqPllGRA4tG7n/GEpL13eNfHChPvGkkUFBMrxSgCAETaTna13G6kq+dMO+SAdbm1A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.890.0", + "@aws-sdk/middleware-host-header": "3.887.0", + "@aws-sdk/middleware-logger": "3.887.0", + "@aws-sdk/middleware-recursion-detection": "3.887.0", + "@aws-sdk/middleware-user-agent": "3.890.0", + "@aws-sdk/region-config-resolver": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@aws-sdk/util-endpoints": "3.890.0", + "@aws-sdk/util-user-agent-browser": "3.887.0", + "@aws-sdk/util-user-agent-node": "3.890.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.2", + "@smithy/middleware-retry": "^4.2.2", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.2", + "@smithy/util-defaults-mode-node": "^4.1.2", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.890.0.tgz", + "integrity": "sha512-VfdT+tkF9groRYNzKvQCsCGDbOQdeBdzyB1d6hWiq22u13UafMIoskJ1ec0i0H1X29oT6mjTitfnvPq1UiKwzQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.890.0.tgz", + "integrity": "sha512-il8kb2/wDLXhemN3p7v4MvbvqoMuo7Ug3ihuIUIhPtSVjcnn+BISJU0S+5YTl8TXf6qxML9VrfxL0pmuhO3BsA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.890.0.tgz", + "integrity": "sha512-+pK/0iQEpPmnztbAw0NNmb+B5pPy8VLu+Ab4SJLgVp41RE9NO13VQtrzUbh61TTAVMrzqWlLQ2qmAl2Fk4VNgw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.890.0", + "@aws-sdk/nested-clients": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.887.0.tgz", + "integrity": "sha512-fmTEJpUhsPsovQ12vZSpVTEP/IaRoJAMBGQXlQNjtCpkBp6Iq3KQDa/HDaPINE+3xxo6XvTdtibsNOd5zJLV9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.873.0.tgz", + "integrity": "sha512-qag+VTqnJWDn8zTAXX4wiVioa0hZDQMtbZcGRERVnLar4/3/VIKBhxX2XibNQXFu1ufgcRn4YntT/XEPecFWcg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.890.0.tgz", + "integrity": "sha512-nJ8v1x9ZQKzMRK4dS4oefOMIHqb6cguctTcx1RB9iTaFOR5pP7bvq+D4mvNZ6vBxiHg1dQGBUUgl5XJmdR7atQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-endpoints": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.873.0.tgz", + "integrity": "sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.887.0.tgz", + "integrity": "sha512-X71UmVsYc6ZTH4KU6hA5urOzYowSXc3qvroagJNLJYU1ilgZ529lP4J9XOYfEvTXkLR1hPFSRxa43SrwgelMjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.887.0", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.890.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.890.0.tgz", + "integrity": "sha512-s85NkCxKoAlUvx7UP7OelxLqwTi27Tps9/Q+4N+9rEUjThxEnDsqJSStJ1XiYhddz1xc/vxMvPjYN0qX6EKPtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.890.0", + "@aws-sdk/types": "3.887.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.887.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.887.0.tgz", + "integrity": "sha512-lMwgWK1kNgUhHGfBvO/5uLe7TKhycwOn3eRCqsKPT9aPCx/HWuTlpcQp8oW2pCRGLS7qzcxqpQulcD+bbUL7XQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz", + "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.1.0.tgz", + "integrity": "sha512-a36AtR7Q7XOhRPt6F/7HENmTWcB8kN7mDJcOFM/+FuKO6x88w8MQJfYCufMWh4fGyVkPjUh3Rrz/dnqFQdo6OQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.1.0.tgz", + "integrity": "sha512-Bnv0B3nSlfB2mPO0WgM49I/prl7+kamF042rrf3ezJ3Z4C7csPYvyYgZfXTGXwXfj1mAwDWjE/ybIf49PzFzvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.2.tgz", + "integrity": "sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.11.0.tgz", + "integrity": "sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.1", + "@smithy/util-utf8": "^4.1.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.2.tgz", + "integrity": "sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.1.1.tgz", + "integrity": "sha512-PwkQw1hZwHTQB6X5hSUWz2OSeuj5Z6enWuAqke7DgWoP3t6vg3ktPpqPz3Erkn6w+tmsl8Oss6nrgyezoea2Iw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.5.0", + "@smithy/util-hex-encoding": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.1.1.tgz", + "integrity": "sha512-Q9QWdAzRaIuVkefupRPRFAasaG/droBqn1feiMnmLa+LLEUG45pqX1+FurHFmlqiCfobB3nUlgoJfeXZsr7MPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.2.1.tgz", + "integrity": "sha512-oSUkF9zDN9zcOUBMtxp8RewJlh71E9NoHWU8jE3hU9JMYCsmW4assVTpgic/iS3/dM317j6hO5x18cc3XrfvEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.1.1.tgz", + "integrity": "sha512-tn6vulwf/ScY0vjhzptSJuDJJqlhNtUjkxJ4wiv9E3SPoEqTEKbaq6bfqRO7nvhTG29ALICRcvfFheOUPl8KNA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.1.1.tgz", + "integrity": "sha512-uLOAiM/Dmgh2CbEXQx+6/ssK7fbzFhd+LjdyFxXid5ZBCbLHTFHLdD/QbXw5aEDsLxQhgzDxLLsZhsftAYwHJA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz", + "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.1.1.tgz", + "integrity": "sha512-avAtk++s1e/1VODf+rg7c9R2pB5G9y8yaJaGY4lPZI2+UIqVyuSDMikWjeWfBVmFZ3O7NpDxBbUCyGhThVUKWQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.1.0", + "@smithy/chunked-blob-reader-native": "^4.1.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", + "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.1.1.tgz", + "integrity": "sha512-3ztT4pV0Moazs3JAYFdfKk11kYFDo4b/3R3+xVjIm6wY9YpJf+xfz+ocEnNKcWAdcmSMqi168i2EMaKmJHbJMA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", + "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz", + "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.1.1.tgz", + "integrity": "sha512-MvWXKK743BuHjr/hnWuT6uStdKEaoqxHAQUvbKJPPZM5ZojTNFI5D+47BoQfBE5RgGlRRty05EbWA+NXDv+hIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", + "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.2.tgz", + "integrity": "sha512-M51KcwD+UeSOFtpALGf5OijWt915aQT5eJhqnMKJt7ZTfDfNcvg2UZgIgTZUoiORawb6o5lk4n3rv7vnzQXgsA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.11.0", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.2.2.tgz", + "integrity": "sha512-KZJueEOO+PWqflv2oGx9jICpHdBYXwCI19j7e2V3IMwKgFcXc9D9q/dsTf4B+uCnYxjNoS1jpyv6pGNGRsKOXA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/service-error-classification": "^4.1.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.1", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz", + "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz", + "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.2.tgz", + "integrity": "sha512-SYGTKyPvyCfEzIN5rD8q/bYaOPZprYUPD2f5g9M7OjaYupWOoQFYJ5ho+0wvxIRf471i2SR4GoiZ2r94Jq9h6A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz", + "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz", + "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz", + "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz", + "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-uri-escape": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz", + "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.1.tgz", + "integrity": "sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.2.0.tgz", + "integrity": "sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.2.1.tgz", + "integrity": "sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.1.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-hex-encoding": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-uri-escape": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.2.tgz", + "integrity": "sha512-u82cjh/x7MlMat76Z38TRmEcG6JtrrxN4N2CSNG5o2v2S3hfLAxRgSgFqf0FKM3dglH41Evknt/HOX+7nfzZ3g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.11.0", + "@smithy/middleware-endpoint": "^4.2.2", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz", + "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz", + "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz", + "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz", + "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", + "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz", + "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz", + "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", - "optional": true, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.2.tgz", + "integrity": "sha512-QKrOw01DvNHKgY+3p4r9Ut4u6EHLVZ01u6SkOMe6V6v5C+nRPXJeWh72qCT1HgwU3O7sxAIu23nNh+FOpYVZKA==", + "license": "Apache-2.0", "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "optional": true, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.2.tgz", + "integrity": "sha512-l2yRmSfx5haYHswPxMmCR6jGwgPs5LjHLuBwlj9U7nNBMS43YV/eevj+Xq1869UYdiynnMrCKtoOYQcwtb6lKg==", + "license": "Apache-2.0", "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "@smithy/config-resolver": "^4.2.2", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.2.tgz", + "integrity": "sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz", + "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz", + "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.1.tgz", + "integrity": "sha512-jGeybqEZ/LIordPLMh5bnmnoIgsqnp4IEimmUp5c5voZ8yx+5kAlN5+juyr7p+f7AtZTgvhmInQk4Q0UVbrZ0Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.1.tgz", + "integrity": "sha512-khKkW/Jqkgh6caxMWbMuox9+YfGlsk9OnHOYCGVEdYQb/XVzcORXHLYUubHmmda0pubEDncofUrPNniS9d+uAA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-hex-encoding": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz", + "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.1.1.tgz", + "integrity": "sha512-PJBmyayrlfxM7nbqjomF4YcT1sApQwZio0NHSsT0EzhJqljRmvhzqZua43TyEs80nJk2Cn2FGPg/N8phH6KeCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -63,6 +1708,30 @@ "node": ">= 6" } }, + "node_modules/@types/node": { + "version": "22.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", + "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -93,68 +1762,331 @@ "debug": "4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/align-text/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/any": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/any/-/any-1.0.0.tgz", + "integrity": "sha512-eHlqaTpbeFyxHAo2H8XI566tY/m+lvRUTsFwBNtWP08qJe7bf16Oj5ab2coiuY9yTMvzFgjpWbuqQIFqH+yi+w==", + "license": "MIT", + "dependencies": { + "for-own": "^0.1.2", + "make-iterator": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", + "license": "MIT", + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map/node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-0.1.1.tgz", + "integrity": "sha512-UeLKGl1CO9AS8+y+d+sCoI4PEFcphx0fGlPiOgzlYAmofYov5fpqvqZdTEk11nXV2E6a6WtepT2gEB0RL2fnmg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "license": "MIT", - "optional": true, "dependencies": { - "humanize-ms": "^1.2.1" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "optional": true, + "node_modules/aws-sdk": { + "version": "2.1692.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1692.0.tgz", + "integrity": "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.6.2" }, "engines": { - "node": ">=8" + "node": ">= 10.0.0" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/aws-sdk/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC", - "optional": true + "node_modules/aws-sdk/node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "license": "BSD-3-Clause" }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, + "node_modules/aws-sdk/node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "*" } }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -182,6 +2114,15 @@ ], "license": "MIT" }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bcryptjs": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", @@ -211,6 +2152,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -231,6 +2178,12 @@ "node": ">=18" } }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -266,12 +2219,39 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -311,6 +2291,24 @@ "node": ">= 10" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -340,6 +2338,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "license": "MIT", + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -369,6 +2386,18 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -376,6 +2405,21 @@ "license": "MIT", "optional": true }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -422,6 +2466,12 @@ "node": ">=6.6.0" } }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -435,6 +2485,18 @@ "node": ">= 0.10" } }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -476,6 +2538,32 @@ "node": ">=4.0.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -501,6 +2589,31 @@ "node": ">=8" } }, + "node_modules/dingbat-to-unicode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", + "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", + "license": "BSD-2-Clause" + }, + "node_modules/docx-pdf": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/docx-pdf/-/docx-pdf-0.0.1.tgz", + "integrity": "sha512-JbQ7w2ZPRzJGY2uLjL5nCAtCtjRoiZS2YYh7eVjax5K6XIFSEalt5FyK3OLwac37qLywtBlx3TG+z1MqYTA0Vw==", + "license": "MIT", + "dependencies": { + "html-pdf": "^2.1.0", + "mammoth": "^1.3.4" + } + }, + "node_modules/docx-to-pdf": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/docx-to-pdf/-/docx-to-pdf-1.1.0.tgz", + "integrity": "sha512-QPgI5d4bPThavlUYl852wP66s5wvHfB1DOTRxOO5eWrRCCpeRj1SohfHZ1gX60YwLvYXJueKzE+tAqxsyj8zQQ==", + "license": "MIT", + "dependencies": { + "request": "^2.40.0" + } + }, "node_modules/dotenv": { "version": "17.2.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", @@ -513,6 +2626,15 @@ "url": "https://dotenvx.com" } }, + "node_modules/duck": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", + "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", + "license": "BSD", + "dependencies": { + "underscore": "^1.13.1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -527,6 +2649,16 @@ "node": ">= 0.4" } }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -624,6 +2756,12 @@ "node": ">= 0.4" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -639,6 +2777,15 @@ "node": ">= 0.6" } }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "license": "MIT", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -648,6 +2795,27 @@ "node": ">=6" } }, + "node_modules/export-dirs": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/export-dirs/-/export-dirs-0.2.4.tgz", + "integrity": "sha512-fBQ2iiw2FPxyzsOOVpWICXUNbuSBanxYYyDy1gaF4Gpfp7UkiuPA6N57Daqf7FpFDyT3g1+L+Rz4tr4aRj9SRw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/export-files": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/export-files/-/export-files-2.1.1.tgz", + "integrity": "sha512-r2x1Zt0OKgdXRy0bXis3sOI8TNYmo5Fe71qXwsvpYaMvIlH5G0fWEf3AYiE2bONjePdSOojca7Jw+p9CQ6/6NQ==", + "license": "MIT", + "dependencies": { + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -683,11 +2851,176 @@ "vary": "^1.1.2" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/extract-zip/node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/extract-zip/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "optional": true + }, + "node_modules/extract-zip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "optional": true, + "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/extract-zip/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", + "optional": true + }, + "node_modules/extract-zip/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", + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, "node_modules/file-uri-to-path": { @@ -713,6 +3046,95 @@ "node": ">= 0.8" } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own/node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -737,6 +3159,18 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -823,6 +3257,15 @@ "node": ">= 0.4" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -870,6 +3313,41 @@ "license": "ISC", "optional": true }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -882,6 +3360,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -889,6 +3382,29 @@ "license": "ISC", "optional": true }, + "node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha512-jZ38TU/EBiGKrmyTNNZgnvCZHNowiRI4+w/I9noMlekHTZH3KyGgvJLmhSgykeAQ9j2SYPDosM0Bg3wHfzibAQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -901,6 +3417,28 @@ "node": ">= 0.4" } }, + "node_modules/html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "license": "MIT" + }, + "node_modules/html-pdf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/html-pdf/-/html-pdf-2.2.0.tgz", + "integrity": "sha512-k4F0znJNVx5ViGEq2HlQIcI8eosSOxFQBbwj8zBqYPT7xx508IDamIhmXiJNha7xuJqRIxn70beDn1qMgYlEdw==", + "deprecated": "Please migrate your projects to a newer library like puppeteer", + "license": "MIT", + "bin": { + "html-pdf": "bin/index.js" + }, + "engines": { + "node": ">=4.0.0" + }, + "optionalDependencies": { + "phantomjs-prebuilt": "^2.1.4" + } + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -948,6 +3486,21 @@ "node": ">= 6" } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1004,6 +3557,12 @@ ], "license": "BSD-3-Clause" }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1074,6 +3633,40 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1084,6 +3677,24 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -1091,12 +3702,96 @@ "license": "MIT", "optional": true }, + "node_modules/is-number": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-1.1.2.tgz", + "integrity": "sha512-dRKHHq76sZAXFf823ziHIOx5fXbuV1IrR892LugLDmyEBVMZzGoske4sY6lf+l3YPH/VyWNiKzNDXAPiQhx9Yg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-1.0.0.tgz", + "integrity": "sha512-+tXodG6I903VrzqxqKAQjA7ti9niaR18pza7uWPLmUvQfsUk+WzNwnu4JnW7UNT3idAcNTGMjZtvdrYTJnWkUA==", + "license": "MIT", + "dependencies": { + "isobject": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object/node_modules/isobject": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-0.2.0.tgz", + "integrity": "sha512-VaWq6XYAsbvM0wf4dyBO7WH9D7GosB7ZZlqrawI9BBiTMINBeCyqSKBa35m870MY3O4aM31pYyZi9DfGrYMJrQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "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/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1104,6 +3799,64 @@ "license": "ISC", "optional": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1126,6 +3879,63 @@ "npm": ">=6" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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/jszip/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/jszip/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/jwa": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", @@ -1147,6 +3957,50 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha512-IG6nm0+QtAMdXt9KvbgbGdvY50RSrw+U4sGZg+KlrSKPJEwVE5JVoI3d7RWfSMdBQneRheeAOj3lIjX5VL/9RQ==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1189,6 +4043,26 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lop": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", + "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", + "license": "BSD-2-Clause", + "dependencies": { + "duck": "^0.1.12", + "option": "~0.2.1", + "underscore": "^1.13.1" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1240,6 +4114,50 @@ "node": ">= 0.6" } }, + "node_modules/make-iterator": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-0.1.1.tgz", + "integrity": "sha512-s8tbTxYqrfcXYHAPxUecPxgBnWod7yFShdSOWiV17WRM87bBH2mzr24A4tpUDv9SqebaV6JsPApwKXnisMmMBA==", + "dependencies": { + "for-own": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mammoth": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.10.0.tgz", + "integrity": "sha512-9HOmqt8uJ5rz7q8XrECU5gRjNftCq4GNG0YIrA6f9iQPCeLgpvgcmRBHi9NQWJQIpT/MAXeg1oKliAK1xoB3eg==", + "license": "BSD-2-Clause", + "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "argparse": "~1.0.3", + "base64-js": "^1.5.1", + "bluebird": "~3.4.0", + "dingbat-to-unicode": "^1.0.1", + "jszip": "^3.7.1", + "lop": "^0.4.2", + "path-is-absolute": "^1.0.0", + "underscore": "^1.13.1", + "xmlbuilder": "^10.0.0" + }, + "bin": { + "mammoth": "bin/mammoth" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/mammoth/node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1444,6 +4362,97 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer-s3": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/multer-s3/-/multer-s3-3.0.1.tgz", + "integrity": "sha512-BFwSO80a5EW4GJRBdUuSHblz2jhVSAze33ZbnGpcfEicoT0iRolx4kWR+AJV07THFRCQ78g+kelKFdjkCCaXeQ==", + "license": "MIT", + "dependencies": { + "@aws-sdk/lib-storage": "^3.46.0", + "file-type": "^3.3.0", + "html-comment-regex": "^1.1.2", + "run-parallel": "^1.1.6" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.0.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -1544,6 +4553,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1565,6 +4583,88 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.defaults": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-0.3.0.tgz", + "integrity": "sha512-VOrsFahJiyB6ajWr5z0C8+mFY47WtWn2w5VFkJHjluy28fg22m2yFZDxu3EmKtf4x38GHFjvmRtPx0aQgTTmQg==", + "license": "MIT", + "dependencies": { + "array-each": "^0.1.0", + "array-slice": "^0.2.3", + "for-own": "^0.1.3", + "isobject": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.defaults/node_modules/isobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz", + "integrity": "sha512-WQQgFoML/sLgmhu9zTekYHZUJaPoa/fpVMQ8oxIuOvppzs70DxxyHZdAIjwcuuNDOVtNYsahhqtBbUvKwhRcGw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.filter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/object.filter/-/object.filter-0.3.0.tgz", + "integrity": "sha512-f/OJtvg2H7ZykQpcCbKTSucLhz9+7pmgJzrI3gPaelCkjkg9Pd+moFN2DFDfx/IWS5f/sMeSqe3xXNnb0MV4DQ==", + "dependencies": { + "for-own": "^0.1.2", + "make-iterator": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-1.1.0.tgz", + "integrity": "sha512-oc6HJYjJhqPa0AsywIBlKNVd9ctu6lrDwr/N4HSpa3FKD1l3cF5pdgdLHm8Fn0zSKGKTKGwVOdoTTgUh1FQkKw==", + "license": "MIT", + "dependencies": { + "for-own": "^0.1.3", + "isobject": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit/node_modules/isobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz", + "integrity": "sha512-WQQgFoML/sLgmhu9zTekYHZUJaPoa/fpVMQ8oxIuOvppzs70DxxyHZdAIjwcuuNDOVtNYsahhqtBbUvKwhRcGw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-0.1.7.tgz", + "integrity": "sha512-5PMRmWAfIbCJavx9xETLSjr0IvHHDOjhW9jnHkOmnTC0Q4d9tWM5GjBAPS+J4yLk2Q+ql/XJBBbBOp2xTzDhDg==", + "license": "MIT", + "dependencies": { + "for-own": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -1586,6 +4686,12 @@ "wrappy": "1" } }, + "node_modules/option": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", + "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==", + "license": "BSD-2-Clause" + }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -1602,6 +4708,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1616,7 +4728,6 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -1630,6 +4741,19 @@ "node": ">=16" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT", + "optional": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, "node_modules/pg": { "version": "8.16.3", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", @@ -1719,6 +4843,74 @@ "split2": "^4.1.0" } }, + "node_modules/phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha512-PIiRzBhW85xco2fuj41FmsyuYHKjKuXWmhjy3A/Y+CMpN/63TV+s9uzfVhsUwFe0G77xWtHBG8xmXf5BqEUEuQ==", + "deprecated": "this package is now deprecated", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" + }, + "bin": { + "phantomjs": "bin/phantomjs" + } + }, + "node_modules/phantomjs-prebuilt/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -1784,6 +4976,21 @@ "node": ">=10" } }, + "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/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -1818,6 +5025,27 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/psl/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -1828,6 +5056,12 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "license": "MIT" + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -1843,6 +5077,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1896,6 +5165,123 @@ "node": ">= 6" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha512-dxdraeZVUNEn9AvLrxkgB2k6buTlym71dJk1fk4v8j3Ou3RKNm07BcgbHdj2lLgYGfqX71F+awb1MR+tWPFJzA==", + "license": "MIT", + "optional": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/request/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/request/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resend": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.4.0.tgz", + "integrity": "sha512-CTr4ix4RI5M/ucL58Wqr+LE8eI4JHtJEFaBAx6yUVNOI3eaPVtJjpNL0G/BdRSWMbwv6CtpprVOY8Xvpp6UJlA==", + "license": "MIT", + "dependencies": { + "svix": "1.76.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -1906,6 +5292,18 @@ "node": ">= 4" } }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "license": "MIT", + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1939,6 +5337,29 @@ "node": ">= 18" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1959,12 +5380,35 @@ ], "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "license": "ISC" + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -2021,6 +5465,29 @@ "license": "ISC", "optional": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -2201,6 +5668,12 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/sqlite3": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", @@ -2225,6 +5698,31 @@ } } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -2247,6 +5745,24 @@ "node": ">= 0.8" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2293,6 +5809,45 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/svix": { + "version": "1.76.1", + "resolved": "https://registry.npmjs.org/svix/-/svix-1.76.1.tgz", + "integrity": "sha512-CRuDWBTgYfDnBLRaZdKp9VuoPcNUq9An14c/k+4YJ15Qc5Grvf66vp0jvTltd4t7OIRj+8lM1DAgvSgvf7hdLw==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "@types/node": "^22.7.5", + "es6-promise": "^4.2.8", + "fast-sha256": "^1.3.0", + "url-parse": "^1.5.10", + "uuid": "^10.0.0" + } + }, + "node_modules/svix/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -2311,9 +5866,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -2353,6 +5908,16 @@ "node": ">=8" } }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2362,6 +5927,34 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2374,6 +5967,12 @@ "node": "*" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -2388,6 +5987,24 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "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/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -2417,12 +6034,112 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "license": "MIT", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utils": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-5ykamACr8s1RAv8oNrADO+zSbaXEJTTm7PygehMAJMDcO4QqCJwwsIvvSxGJwN/E3zUjpIpngy7zoZ0P5E/wAw==", + "license": "MIT", + "dependencies": { + "any": "^1.0.0", + "arr-diff": "^1.0.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "arr-union": "^2.0.1", + "array-each": "^0.1.1", + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "center-align": "^0.1.1", + "export-dirs": "^0.2.4", + "export-files": "^2.0.1", + "for-in": "^0.1.4", + "for-own": "^0.1.3", + "has-values": "^0.1.3", + "is-number": "^1.1.2", + "is-plain-object": "^1.0.0", + "kind-of": "^1.1.0", + "make-iterator": "^0.1.1", + "object.defaults": "^0.3.0", + "object.filter": "^0.3.0", + "object.omit": "^1.1.0", + "object.pick": "^1.1.1", + "object.reduce": "^0.1.7", + "right-align": "^0.1.1", + "word-wrap": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2432,6 +6149,20 @@ "node": ">= 0.8" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2448,6 +6179,27 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -2458,12 +6210,43 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.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/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -2478,6 +6261,17 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } } } } diff --git a/backend/package.json b/backend/package.json index 87ff954..5fb89b0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,14 +11,22 @@ "license": "ISC", "type": "commonjs", "dependencies": { + "@aws-sdk/client-s3": "^3.890.0", + "aws-sdk": "^2.1692.0", "bcryptjs": "^3.0.2", "body-parser": "^2.2.0", "cors": "^2.8.5", + "docx-pdf": "^0.0.1", + "docx-to-pdf": "^1.1.0", "dotenv": "^17.2.1", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", + "multer": "^2.0.2", + "multer-s3": "^3.0.1", "nodemailer": "^7.0.5", "pg": "^8.16.3", - "sqlite3": "^5.1.7" + "resend": "^6.4.0", + "sqlite3": "^5.1.7", + "utils": "^0.2.2" } } diff --git a/backend/server.js b/backend/server.js index e7c6a78..1690626 100644 --- a/backend/server.js +++ b/backend/server.js @@ -14,9 +14,16 @@ const bodyParser = require("body-parser"); const bcrypt = require("bcryptjs"); const jwt = require("jsonwebtoken"); const pool = require("./database.js"); -const nodemailer = require("nodemailer"); +//const nodemailer = require("nodemailer"); +const { Resend } = require('resend'); +const resend = new Resend(process.env.RESEND_API_KEY); const path = require("path"); +// --- NEW REQUIRES FOR AWS SDK v3 --- +const { S3Client } = require('@aws-sdk/client-s3'); +const multer = require('multer'); +const multerS3 = require('multer-s3'); + const app = express(); const PORT = process.env.PORT || 5000; const SECRET = process.env.JWT_SECRET || "supersecret"; // use .env in prod @@ -26,30 +33,58 @@ const verificationCodes = {}; // Middleware Setup const allowedOrigins = [ - "http://localhost:5173", - "https://markingapp-frontend.onrender.com" + "http://localhost:5173", + "https://markingapp-frontend.onrender.com" ]; app.use( cors({ origin: function (origin, callback) { - if (!origin) return callback(null, true); - if (allowedOrigins.includes(origin)) callback(null, true); - else callback(new Error("Not allowed by CORS")); + if (!origin) return callback(null, true); + if (allowedOrigins.includes(origin)) { + callback(null, true); + } else { + callback(new Error("Not allowed by CORS")); + } }, + credentials: false, }) ); app.use(bodyParser.json()); -app.use("/uploads", express.static(path.join(process.cwd(), "uploads"))); + // Nodemailer Configuration -const transporter = nodemailer.createTransport({ +/*const transporter = nodemailer.createTransport({ service: "Gmail", auth: { user: process.env.EMAIL_USER || "markingapp3077@gmail.com", pass: process.env.EMAIL_PASS || "mche wvuu wkbh nxbi", }, +});*/ + + + +// --- NEW: AWS SDK v3 & MULTER CONFIGURATION --- +// The S3Client will automatically read credentials from your .env file +// as long as the variable names are correct (AWS_ACCESS_KEY_ID, etc.) +const s3Client = new S3Client({ + region: process.env.AWS_REGION // The region from your .env file +}); + +// Configure multer-s3 to use the v3 client +const upload = multer({ + storage: multerS3({ + s3: s3Client, // Pass the v3 client here + bucket: process.env.AWS_S3_BUCKET_NAME, + metadata: function (req, file, cb) { + cb(null, { fieldName: file.fieldname }); + }, + key: function (req, file, cb) { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); + } + }) }); /////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -76,7 +111,7 @@ function authenticateToken(req, res, next) { // Signup Flow ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -app.post("/send-code", async (req, res) => { +/*app.post("/send-code", async (req, res) => { const { email } = req.body; if (!email) return res.status(400).json({ message: "Email required" }); @@ -90,6 +125,28 @@ app.post("/send-code", async (req, res) => { subject: "Your verification code", text: `Your verification code is: ${code}. This code will expire in 10 minutes`, }); + res.json({ message: "Verification code sent" }); + } catch (err) { + console.error("Error sending email:", err); + res.status(500).json({ message: "Error sending email" }); + } +});*/ + +app.post("/send-code", async (req, res) => { + const { email } = req.body; + if (!email) return res.status(400).json({ message: "Email required" }); + + const code = Math.floor(100000 + Math.random() * 900000).toString(); + verificationCodes[email] = { code, expires: Date.now() + 10 * 60 * 1000 }; + + try { + await resend.emails.send({ + from: 'Marking App ', + to: email, + subject: "Your verification code", + html: `

Your verification code is: ${code}. This code will expire in 10 minutes.

`, + }); + res.json({ message: "Verification code sent" }); } catch (err) { console.error("Error sending email:", err); @@ -139,10 +196,10 @@ app.post("/login", async (req, res) => { const token = generateToken(user); // Include minimal user info in the response - res.json({ - message: "Login successful", - token, - user: { id: user.id, username: user.username } + res.json({ + message: "Login successful", + token, + user: { id: user.id, username: user.username } }); } catch (err) { console.error(err); @@ -211,8 +268,8 @@ app.post("/resend-code", async (req, res) => { verificationCodes[email] = { code, expires: Date.now() + 5 * 60 * 1000 }; try { - await transporter.sendMail({ - from: process.env.EMAIL_USER, + await resend.emails.send({ + from: 'Marking App ', to: email, subject: "Your verification code", text: `Your code is: ${code}. This code will expire in 5 minutes`, @@ -302,6 +359,7 @@ app.get("/team/:teamId", authenticateToken, async (req, res) => { } }); +// Fetch all members of a team app.get("/team/:teamId/members", authenticateToken, async (req, res) => { const { teamId } = req.params; try { @@ -313,11 +371,6 @@ app.get("/team/:teamId/members", authenticateToken, async (req, res) => { [teamId] ); - // --- DEBUG LINES --- - console.log("Requested teamId:", teamId); - console.log("Query result rows:", result.rows); - // ------------------- - if (result.rows.length === 0) return res.status(404).json({ error: "No team members found" }); res.json({ members: result.rows }); } catch (err) { @@ -327,9 +380,10 @@ app.get("/team/:teamId/members", authenticateToken, async (req, res) => { }); // Invite multiple users to a team -app.post("/team/:teamId/invite", authenticateToken, async (req, res) => { +/*app.post("/team/:teamId/invite", authenticateToken, async (req, res) => { const { teamId } = req.params; - const { emails } = req.body; // expects an array of emails + // Destructure both emails and the new message field + const { emails, message } = req.body; const inviterId = req.user.id; if (!emails || !Array.isArray(emails) || emails.length === 0) { @@ -339,49 +393,65 @@ app.post("/team/:teamId/invite", authenticateToken, async (req, res) => { try { const results = []; for (const email of emails) { - // 1. Check if user with this email is already in the team + // 1. Check if user is already a member (no changes needed here) const memberCheck = await pool.query( - `SELECT u.id - FROM users u - JOIN team_members tm ON u.id = tm.user_id - WHERE tm.team_id = $1 AND u.username = $2`, + `SELECT u.id FROM users u JOIN team_members tm ON u.id = tm.user_id WHERE tm.team_id = $1 AND u.username = $2`, [teamId, email] ); if (memberCheck.rows.length > 0) { results.push({ email, status: "already_member" }); - continue; // skip to next email + continue; } - // 2. Check if invite already exists and is still pending + // 2. Check for pending invites (no changes needed here) const inviteCheck = await pool.query( - `SELECT id FROM team_invites - WHERE team_id=$1 AND invitee_email=$2 AND status='pending'`, + `SELECT id FROM team_invites WHERE team_id=$1 AND invitee_email=$2 AND status='pending'`, [teamId, email] ); if (inviteCheck.rows.length > 0) { results.push({ email, status: "already_invited" }); - continue; // skip to next email + continue; } - // 3. Create invite + send mail + // 3. Create invite + send mail (MODIFIED SECTION) const inviteToken = require("crypto").randomBytes(32).toString("hex"); await pool.query( - `INSERT INTO team_invites (team_id, inviter_id, invitee_email, token, status) - VALUES ($1, $2, $3, $4, 'pending')`, + `INSERT INTO team_invites (team_id, inviter_id, invitee_email, token, status) VALUES ($1, $2, $3, $4, 'pending')`, [teamId, inviterId, email, inviteToken] ); - const inviteUrl = `${process.env.FRONTEND_URL}join-team?token=${inviteToken}`; - await transporter.sendMail({ - from: process.env.EMAIL_USER, + const inviteUrl = `${process.env.FRONTEND_URL}/join-team?token=${inviteToken}`; + + // --- Start of email message logic --- + // Build the email HTML dynamically + let emailHtml = `

You have been invited to join a team.

`; + + // If a custom message exists, add it to the email body + if (message && message.trim() !== "") { + emailHtml += ` +
+

A message from the inviter:

+

${message}

+
+ `; + } + + emailHtml += ` +

Click the button below to accept the invitation:

+ + Join Team + + `; + // --- End of email message logic --- + + await resend.emails.send({ + from: 'Marking App ', to: email, subject: "You're invited to join a team!", - html: `

You have been invited to join a team.

-

Click below to accept the invitation:

- Join Team`, + html: emailHtml, // Use the dynamically created HTML }); results.push({ email, status: "sent" }); @@ -392,8 +462,111 @@ app.post("/team/:teamId/invite", authenticateToken, async (req, res) => { console.error("Error sending invites:", err); res.status(500).json({ error: "Failed to send invites" }); } -}); +});*/ +app.post("/team/:teamId/invite", authenticateToken, async (req, res) => { + const { teamId } = req.params; + const { emails, message } = req.body; + const inviterId = req.user.id; + + console.log('=== INVITE REQUEST START ==='); + console.log('Team ID:', teamId); + console.log('Emails:', emails); + console.log('Message:', message); + console.log('Inviter ID:', inviterId); + console.log('Request body:', req.body); + + if (!emails || !Array.isArray(emails) || emails.length === 0) { + console.log('No emails provided'); + return res.status(400).json({ error: "No emails provided." }); + } + + try { + const results = []; + for (const email of emails) { + console.log(`\n--- Processing email: ${email} ---`); + + // 1. Check if user exists in system + const userCheck = await pool.query("SELECT id FROM users WHERE username=$1", [email]); + console.log(`User exists in system: ${userCheck.rows.length > 0}`); + + // 2. Check if user is already a team member + const memberCheck = await pool.query( + `SELECT u.id FROM users u JOIN team_members tm ON u.id = tm.user_id WHERE tm.team_id = $1 AND u.username = $2`, + [teamId, email] + ); + + if (memberCheck.rows.length > 0) { + console.log(`User ${email} is already a team member`); + results.push({ email, status: "already_member" }); + continue; + } + + // 3. Check for pending invites + const inviteCheck = await pool.query( + `SELECT id FROM team_invites WHERE team_id=$1 AND invitee_email=$2 AND status='pending'`, + [teamId, email] + ); + + if (inviteCheck.rows.length > 0) { + console.log(`User ${email} already has a pending invite`); + results.push({ email, status: "already_invited" }); + continue; + } + // 4. Create invite + const inviteToken = require("crypto").randomBytes(32).toString("hex"); + console.log(`Creating invite with token: ${inviteToken}`); + + const inviteResult = await pool.query( + `INSERT INTO team_invites (team_id, inviter_id, invitee_email, token, status) VALUES ($1, $2, $3, $4, 'pending') RETURNING id`, + [teamId, inviterId, email, inviteToken] + ); + + console.log(`Invite created with ID: ${inviteResult.rows[0].id}`); + + const inviteUrl = `${process.env.FRONTEND_URL}/join-team?token=${inviteToken}`; + console.log(`Invite URL: ${inviteUrl}`); + + // Build email HTML + let emailHtml = `

You have been invited to join a team.

`; + + if (message && message.trim() !== "") { + emailHtml += ` +
+

A message from the inviter:

+

${message}

+
+ `; + } + + + + // 5. Send email + try { + console.log(`Sending email to: ${email}`); + const emailResult = await resend.emails.send({ + from: 'Marking App ', + to: email, + subject: "You're invited to join a team!", + html: emailHtml, + }); + console.log(`Email sent successfully to ${email}`); + results.push({ email, status: "sent" }); + } catch (emailErr) { + console.error(`Failed to send email to ${email}:`, emailErr); + results.push({ email, status: "email_failed", error: emailErr.message }); + } + } + + console.log('=== INVITE PROCESS COMPLETED ==='); + console.log('Results:', results); + res.json({ message: "Invitation process complete", results }); + } catch (err) { + console.error("Error sending invites:", err); + console.error("Error stack:", err.stack); + res.status(500).json({ error: "Failed to send invites: " + err.message }); + } +}); // Accept or deny an invite app.post("/team/invite/:token/respond", authenticateToken, async (req, res) => { @@ -435,6 +608,8 @@ app.post("/team/invite/:token/respond", authenticateToken, async (req, res) => { } }); + + // Get invite details by token app.get("/team/invite/:token", async (req, res) => { const { token } = req.params; @@ -465,13 +640,12 @@ app.get("/team/invite/:token", async (req, res) => { } }); -// Get all assignments for a team app.get("/team/:teamId/assignments", authenticateToken, async (req, res) => { const { teamId } = req.params; try { const assignmentsRes = await pool.query( - `SELECT id, title, due_date, created_by + `SELECT id, course_code, course_name, semester, due_date, created_by, status FROM assignments WHERE team_id=$1 ORDER BY due_date ASC`, @@ -485,6 +659,1374 @@ app.get("/team/:teamId/assignments", authenticateToken, async (req, res) => { } }); +// Get a SINGLE, detailed assignment with its full rubric and markers (COMPLETELY REWRITTEN) +// This endpoint now fetches data from multiple tables and assembles a complete object. +app.get("/team/:teamId/assignments/:assignmentId", authenticateToken, async (req, res) => { + const { assignmentId, teamId } = req.params; + const userId = req.user.id; // from authenticateToken + + try { + // Step 1: Fetch the main assignment details. + // We also verify that the user is a member of the team that owns the assignment. + const assignmentQuery = pool.query( + `SELECT a.id, a.course_code, a.course_name, a.semester, a.due_date, a.created_by + FROM assignments a + JOIN team_members tm ON a.team_id = tm.team_id + WHERE a.id = $1 AND a.team_id = $2 AND tm.user_id = $3`, + [assignmentId, teamId, userId] + ); + + // Step 2: Fetch the assigned markers for this assignment. + const markersQuery = pool.query( + `SELECT u.id, u.username + FROM assignment_markers am + JOIN users u ON am.user_id = u.id + WHERE am.assignment_id = $1`, + [assignmentId] + ); + + // Step 3: Fetch all rubric criteria for this assignment. + const criteriaQuery = pool.query( + `SELECT id, criterion_description, points, deviation_threshold + FROM rubric_criteria + WHERE assignment_id = $1 + ORDER BY id ASC`, // Keep a consistent order + [assignmentId] + ); + + // Step 4: Fetch all rubric tiers for all criteria in this assignment. + const tiersQuery = pool.query( + `SELECT T.id, T.criterion_id, T.tier_name, T.description, T.lower_bound, T.upper_bound + FROM rubric_tiers T + JOIN rubric_criteria C ON T.criterion_id = C.id + WHERE C.assignment_id = $1 + ORDER BY T.criterion_id ASC, T.upper_bound DESC`, // Order is important for assembly + [assignmentId] + ); + + // Run all queries in parallel for better performance + const [assignmentRes, markersRes, criteriaRes, tiersRes] = await Promise.all([ + assignmentQuery, + markersQuery, + criteriaQuery, + tiersQuery + ]); + + // Check if assignment exists and if user has access + if (assignmentRes.rows.length === 0) { + return res.status(404).json({ error: "Assignment not found or you do not have access." }); + } + + // Step 5: Assemble the final JSON object. + const criteriaMap = new Map(); + // Initialize each criterion with an empty tiers array + criteriaRes.rows.forEach(criterion => { + criterion.tiers = []; + criteriaMap.set(criterion.id, criterion); + }); + + // Populate the tiers for each criterion + tiersRes.rows.forEach(tier => { + if (criteriaMap.has(tier.criterion_id)) { + criteriaMap.get(tier.criterion_id).tiers.push(tier); + } + }); + + const finalRubric = Array.from(criteriaMap.values()); + + const finalResponse = { + assignment: assignmentRes.rows[0], + markers: markersRes.rows, + rubric: finalRubric, + }; + + res.json(finalResponse); + + } catch (err) { + console.error(`Failed to fetch details for assignment ${assignmentId}:`, err); + res.status(500).json({ error: "Failed to fetch assignment details" }); + } +}); + +//////////////////////////////////////////////////////////////////// +// Assignments - NEW SECTION +//////////////////////////////////////////////////////////////////// + +// ---------------------------------------------------------------------------------- +// FINAL ENDPOINT for Assignment Creation with AWS SDK v3 +// This is the full code for the endpoint that handles the multi-step form, +// including the S3 file uploads for control papers. +// ---------------------------------------------------------------------------------- + +app.post("/assignments", authenticateToken, + upload.single('controlPaper'), async (req, res) => { + const client = await pool.connect(); + try { + // STEP 1: PARSE INCOMING DATA + const file = req.file; + const createdById = req.user.id; + const { assignmentDetails, markers, rubric } = JSON.parse(req.body.assignmentData); + + + + if (!file) { + return res.status(400).json({ message: "A control paper must be uploaded." }); + } + + + const fs = require('fs'); + const path = require('path'); + const { GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3'); + const docxConverter = require('docx-pdf'); + const { v4: uuidv4 } = require('uuid'); + + // --- Helper: sanitize filenames for Windows --- + const sanitizeFileName = (name) => name.replace(/[^a-zA-Z0-9.-]/g, '_'); + + // --- Helper: download S3 file locally --- + const downloadS3File = async (bucket, key, originalName) => { + const safeName = sanitizeFileName(originalName); + const tempDir = path.join(__dirname, 'temp'); + if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }); + const tempPath = path.join(tempDir, safeName); + + const command = new GetObjectCommand({ Bucket: bucket, Key: key }); + const s3Object = await s3Client.send(command); + + await new Promise((resolve, reject) => { + const writeStream = fs.createWriteStream(tempPath); + s3Object.Body.pipe(writeStream) + .on('finish', resolve) + .on('error', reject); + }); + + return tempPath; + }; + + // --- Helper: convert doc/docx to PDF (skip non-Word files) --- + const convertToPdf = async (inputPath) => { + if (!/\.(doc|docx)$/i.test(inputPath)) { + console.warn(`Skipping conversion for non-Word file: ${inputPath}`); + return inputPath; // Already a PDF or unsupported format + } + + const outputPath = inputPath.replace(/\.(doc|docx)$/i, '.pdf'); + + await new Promise((resolve, reject) => { + const originalConsoleWarn = console.warn; + console.warn = () => { }; // suppress docx-pdf warnings + + docxConverter(inputPath, outputPath, (err, result) => { + console.warn = originalConsoleWarn; + if (err) reject(err); + else resolve(result); + }); + }); + + return outputPath; + }; + + // --- Helper: upload PDF to S3 --- + const uploadPdfToS3 = async (pdfPath, originalName) => { + if (!pdfPath || !fs.existsSync(pdfPath)) return null; + + const pdfKey = `pdfs/${uuidv4()}-${sanitizeFileName(path.basename(originalName, path.extname(originalName)))}.pdf`; + const fileContent = fs.readFileSync(pdfPath); + + await s3Client.send(new PutObjectCommand({ + Bucket: process.env.AWS_S3_BUCKET_NAME, + Key: pdfKey, + Body: fileContent, + ContentType: 'application/pdf', + })); + + return `https://${process.env.AWS_S3_BUCKET_NAME}.s3.amazonaws.com/${pdfKey}`; + }; + + // --- Cleanup temp files safely --- + const cleanupFiles = (files) => { + files.forEach(file => { + if (file && fs.existsSync(file)) { + try { fs.unlinkSync(file); } + catch (err) { console.warn('Failed to delete temp file:', file, err.message); } + } + }); + }; + + // --- PROCESS FILES --- + const local = await downloadS3File(file.bucket, file.key, file.originalname); + //const local = file.path; + + + const pdfPath = await convertToPdf(local); + + + const controlPaperPath = await uploadPdfToS3(pdfPath, file.originalname); + + if (!controlPaperPath) { + throw new Error("Failed to upload control paper PDF to S3."); + } + + + // --- DATABASE TRANSACTION STARTS --- + await client.query('BEGIN'); + + // STEP 2: Insert the main assignment details + const assignmentSql = ` + INSERT INTO assignments (team_id, created_by, course_code, course_name, semester, due_date) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING id; + `; + const assignmentValues = [ + assignmentDetails.teamId, + createdById, + assignmentDetails.courseCode, + assignmentDetails.courseName, + assignmentDetails.semester, + assignmentDetails.dueDate, + ]; + const newAssignment = await client.query(assignmentSql, assignmentValues); + const newAssignmentId = newAssignment.rows[0].id; + + // STEP 3: Create control paper submissions + const submissionsSql = ` + INSERT INTO submissions (assignment_id, student_identifier, is_control_paper, file_path) + VALUES ($1, 'cp-A', TRUE, $2); + `; + await client.query(submissionsSql, [newAssignmentId, controlPaperPath]); + + + // STEP 4: Insert assigned markers + if (markers?.length > 0) { + const markerSql = 'INSERT INTO assignment_markers (assignment_id, user_id) VALUES ($1, $2);'; + for (const markerId of markers) await client.query(markerSql, [newAssignmentId, markerId]); + } + + // STEP 5: Insert rubric criteria and tiers + const criteriaSql = ` + INSERT INTO rubric_criteria (assignment_id, criterion_description, points, deviation_threshold) + VALUES ($1, $2, $3, $4) RETURNING id; + `; + const tierSql = ` + INSERT INTO rubric_tiers (criterion_id, tier_name, description, lower_bound, upper_bound) + VALUES ($1, $2, $3, $4, $5); + `; + for (const criterion of rubric) { + const deviationPct = Number(criterion.deviation); + if (!Number.isFinite(deviationPct) || deviationPct < 0 || deviationPct > 100) { + await client.query('ROLLBACK'); + return res.status(400).json({ message: `Invalid deviation percentage: ${criterion.deviation}. Must be between 0 and 100.` }); + } + + const criteriaValues = [newAssignmentId, criterion.criteria, criterion.points, deviationPct]; + const newCriterion = await client.query(criteriaSql, criteriaValues); + const newCriterionId = newCriterion.rows[0].id; + + for (const tier of criterion.tiers) { + const tierValues = [newCriterionId, tier.name, tier.description, tier.lowerBound, tier.upperBound]; + await client.query(tierSql, tierValues); + } + } + + // --- COMMIT TRANSACTION --- + await client.query('COMMIT'); + cleanupFiles([local, pdfPath]); + res.status(201).json({ message: 'Assignment created successfully!', assignmentId: newAssignmentId }); + + + } catch (error) { + await client.query('ROLLBACK'); + console.error('Error creating assignment:', error.message); + console.error(error.stack); + res.status(500).json({ + message: `Server error: ${error.message}`, + }); +} finally { + client.release(); +} + +}); + + + +//////////////////////////////////////////////////////////////////// +// Assignments deletion +//////////////////////////////////////////////////////////////////// + +app.delete("/assignments/:id", authenticateToken, async (req, res) => { + const { DeleteObjectCommand } = require('@aws-sdk/client-s3'); + const client = await pool.connect(); + const assignmentId = req.params.id; + + try { + // --- DATABASE TRANSACTION STARTS --- + await client.query('BEGIN'); + + // 1. Grab control paper file paths so we can remove them from S3 afterwards + const submissionRes = await client.query( + `SELECT file_path + FROM submissions + WHERE assignment_id = $1 AND is_control_paper = TRUE`, + [assignmentId] + ); + const filePaths = submissionRes.rows.map(r => r.file_path); + + // 2. Delete rubric tiers first (they depend on criteria) + await client.query(` + DELETE FROM rubric_tiers + WHERE criterion_id IN ( + SELECT id FROM rubric_criteria WHERE assignment_id = $1 + ); + `, [assignmentId]); + + // 3. Delete rubric criteria + await client.query('DELETE FROM rubric_criteria WHERE assignment_id = $1;', [assignmentId]); + + // 4. Delete assignment_markers + await client.query('DELETE FROM assignment_markers WHERE assignment_id = $1;', [assignmentId]); + + // 5. Delete submissions + await client.query('DELETE FROM submissions WHERE assignment_id = $1;', [assignmentId]); + + // 6. Delete assignment itself + await client.query('DELETE FROM assignments WHERE id = $1;', [assignmentId]); + + // --- DATABASE TRANSACTION ENDS (SUCCESS) --- + await client.query('COMMIT'); + + // 7. Delete the control paper files from S3 (outside the transaction) + for (const filePath of filePaths) { + try { + // filePath looks like https://bucket.s3.amazonaws.com/pdfs/uuid-filename.pdf + const urlParts = new URL(filePath); + const bucket = process.env.AWS_S3_BUCKET_NAME; + const key = decodeURIComponent(urlParts.pathname.slice(1)); // strip leading "/" + + await s3Client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key })); + console.log('Deleted S3 object:', key); + } catch (err) { + console.warn('Failed to delete S3 object:', filePath, err.message); + } + } + + res.status(200).json({ message: 'Assignment and control papers deleted successfully.' }); + } catch (error) { + await client.query('ROLLBACK'); + console.error('Error deleting assignment:', error); + res.status(500).json({ message: 'Failed to delete assignment due to a server error.' }); + } finally { + client.release(); + } +}); + + +//////////////////////////////////////////////////////////////////// +// Admin Comments on Rubric Criteria +//////////////////////////////////////////////////////////////////// + + +app.post("/team/:teamId/assignments/:assignmentId/rubric-criteria/:criterionId/admin-comment", authenticateToken, async (req, res) => { + const { teamId, assignmentId, criterionId } = req.params; + const { adminComment } = req.body; + //console.log("Updating admin comment:", { teamId, assignmentId, criterionId, adminComment }); + try { + const result = await pool.query(` + UPDATE rubric_criteria + SET admin_comments = $1 + WHERE id = $2 AND assignment_id = $3 + RETURNING id, criterion_description, points, deviation_threshold, admin_comments + `, [adminComment, criterionId, assignmentId]); + + if (result.rows.length === 0) { + return res.status(404).json({ message: "Rubric criterion not found" }); + } + + res.json({ + message: "Admin comment updated successfully", + rubricCriterion: result.rows[0] + }); + } catch (err) { + console.error("Error updating admin comment:", err); + res.status(500).json({ message: "Failed to update admin comment." }); + } +}); + +//////////////////////////////////////////////////////////////////// +// User Management +//////////////////////////////////////////////////////////////////// + +// New endpoint to fetch a single user by ID +app.get("/users/:id", authenticateToken, async (req, res) => { + const userId = req.params.id; + const requestingUserId = req.user.id; // The ID of the user making the request + + // Optional: Add a check if the requesting user is allowed to view this user's profile. + // For simplicity, we'll allow any authenticated user to fetch details of any user, + // but in a real app, you might only allow fetching your own profile or profiles + // of users within your team, or by an admin. + // If you want to restrict it to only fetching their own profile: + // if (String(userId) !== String(requestingUserId)) { + // return res.status(403).json({ message: "Access denied: Cannot view other users' profiles." }); + // } + + + try { + const result = await pool.query("SELECT id, username FROM users WHERE id=$1", [userId]); + const user = result.rows[0]; + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + res.json(user); // Return id and username + } catch (err) { + console.error(`Error fetching user with ID ${userId}:`, err); + res.status(500).json({ message: "Failed to fetch user details" }); + } +}); + + + +//////////////////////////////////////////////////// +// User Role checking +//////////////////////////////////////////////////// +app.get("/team/:teamId/role", authenticateToken, async (req, res) => { + const { teamId } = req.params; + const currentUserId = req.user.id; + + try { + const teamIdInt = parseInt(teamId, 10); + const result = await pool.query( + `SELECT role FROM team_members WHERE user_id = $1 AND team_id = $2`, + [currentUserId, teamIdInt] + ); + + + + if (result.rows.length === 0) { + return res.status(404).json({ message: "Role not found for this team" }); + } + + res.json({ role: result.rows[0].role }); + } catch (error) { + console.error("Error fetching user role:", error); + res.status(500).json({ message: "Server error while fetching user role" }); + } +}); + + +//////////////////////////////////////////////////// +// Assignment Stuff +//////////////////////////////////////////////////// + +// ---------------------------------------------------------------------------------- +// FINAL, COMPLETE 'DETAILS' ENDPOINT WITH ROLE DETECTION +// This is the full code for the endpoint that serves the Assignment Details page. +// It includes all queries for assignment data, markers, rubric, marks, file paths, +// and the current user's specific role within the team. +// ---------------------------------------------------------------------------------- +app.get("/team/:teamId/assignments/:assignmentId/details", authenticateToken, async (req, res) => { + const { assignmentId, teamId } = req.params; + const currentUserId = req.user.id; // Get the user's global ID from the token + + try { + // --- STEP 1: DEFINE ALL DATABASE QUERIES --- + + // Query 1: Get main assignment details, including the creator's ID, and verify the current user is a member of the team. + const assignmentQuery = pool.query( + `SELECT a.id, a.course_code, a.course_name, a.semester, a.due_date, a.created_by + FROM assignments a + JOIN team_members tm ON a.team_id = tm.team_id + WHERE a.id = $1 AND a.team_id = $2 AND tm.user_id = $3`, + [assignmentId, teamId, currentUserId] + ); + + // Query 2: Get all markers who are specifically assigned to this assignment. + const markersQuery = pool.query( + `SELECT u.id, u.username + FROM assignment_markers am + JOIN users u ON am.user_id = u.id + WHERE am.assignment_id = $1`, + [assignmentId] + ); + + // Query 3: Get all rubric criteria for the assignment. + const criteriaQuery = pool.query( + `SELECT id, criterion_description, points, deviation_threshold, admin_comments + FROM rubric_criteria + WHERE assignment_id = $1 + ORDER BY id ASC`, + [assignmentId] + ); + + // Query 4: Get all submitted marks for the control papers associated with this assignment. + const marksQuery = pool.query( + `SELECT + m.tutor_id as "marker_id", + s.student_identifier as "paper_id", + m.criterion_id, + m.marks_awarded as "score" + FROM marks m + JOIN submissions s ON m.submission_id = s.id + WHERE s.assignment_id = $1 AND s.is_control_paper = TRUE`, + [assignmentId] + ); + + // Query 5: Get the control paper submissions themselves to retrieve their file paths from S3. + const submissionsQuery = pool.query( + `SELECT student_identifier, file_path + FROM submissions + WHERE assignment_id = $1 AND is_control_paper = TRUE`, + [assignmentId] + ); + + + // Query 7: Count how many unique markers have submitted marks for any control paper + const markersAlreadyMarkedQuery = pool.query( + `SELECT COUNT(*) AS graded_marker_count + FROM ( + SELECT m.tutor_id + FROM submissions s + JOIN marks m ON m.submission_id = s.id + WHERE s.assignment_id = $1 + GROUP BY m.tutor_id + HAVING COUNT(DISTINCT s.id) = ( + SELECT COUNT(*) FROM submissions WHERE assignment_id = $1 + ) + ) fully_marked_tutors;`, + [assignmentId] + ); + + + // Query 8: Get the current user's role ('admin' or 'tutor') for THIS specific team. + const userRoleQuery = pool.query( + `SELECT role FROM team_members WHERE user_id = $1 AND team_id = $2`, + [currentUserId, teamId] + ); + + // Query 9: Get current user's completion status for this assignment (from assignment_markers) + const personalStatusQuery = pool.query( + `SELECT completed + FROM assignment_markers + WHERE assignment_id = $1 AND user_id = $2`, + [assignmentId, currentUserId] + ); + + // Query 10: Get all rubric tiers for all criteria in this assignment (needed for marking page) + const tiersQuery = pool.query( + `SELECT T.id, T.criterion_id, T.tier_name, T.description, T.lower_bound, T.upper_bound + FROM rubric_tiers T + JOIN rubric_criteria C ON T.criterion_id = C.id + WHERE C.assignment_id = $1 + ORDER BY T.criterion_id ASC, T.upper_bound DESC`, + [assignmentId] + ); + + // --- STEP 2: EXECUTE ALL QUERIES IN PARALLEL FOR PERFORMANCE --- + const [ + assignmentRes, + markersRes, + criteriaRes, + marksRes, + submissionsRes, + userRoleRes, + markersAlreadyMarkedRes, + personalStatusRes, + tiersRes + ] = await Promise.all([ + assignmentQuery, + markersQuery, + criteriaQuery, + marksQuery, + submissionsQuery, + userRoleQuery, + markersAlreadyMarkedQuery, + personalStatusQuery, + tiersQuery + ]); + + + // If the assignment query returns no rows, the user either doesn't have access or the assignment doesn't exist. + if (assignmentRes.rows.length === 0) { + return res.status(404).json({ message: "Assignment not found or you do not have access." }); + } + + // Extract the role from the new query result. Default to 'tutor' as a safe fallback. + const currentUserRole = userRoleRes.rows[0]?.role || 'tutor'; + + // --- STEP 3: ASSEMBLE THE FINAL JSON PAYLOAD --- + + // Assemble rubric criteria with their tiers + const criteriaMap = new Map(); + criteriaRes.rows.forEach(criterion => { + criteriaMap.set(criterion.id, { + id: criterion.id, + categoryName: criterion.criterion_description, + maxScore: parseFloat(criterion.points), + deviationScore: parseFloat(criterion.deviation_threshold), + adminComments: criterion.admin_comments, + tiers: [] + }); + }); + + // Populate tiers for each criterion + tiersRes.rows.forEach(tier => { + if (criteriaMap.has(tier.criterion_id)) { + criteriaMap.get(tier.criterion_id).tiers.push({ + name: tier.tier_name, + description: tier.description, + lowerBound: parseFloat(tier.lower_bound), + upperBound: parseFloat(tier.upper_bound) + }); + } + }); + + const rubricWithTiers = Array.from(criteriaMap.values()); + + // A) Assemble the control paper data, including their file paths and any submitted marks. + const controlPapersMap = new Map(); + const filePaths = {}; + submissionsRes.rows.forEach(row => { + filePaths[row.student_identifier] = row.file_path; + }); + + // Initialize the paper objects with their file paths. + controlPapersMap.set('cp-A', { id: 'cp-A', name: 'Control Paper', marks: [], filePath: filePaths['cp-A'] || null }); + + // Group the raw marks data by marker and paper for easy consumption by the frontend. + const marksByMarkerAndPaper = new Map(); + marksRes.rows.forEach(mark => { + const key = `${mark.marker_id}|${mark.paper_id}`; + if (!marksByMarkerAndPaper.has(key)) { + marksByMarkerAndPaper.set(key, { markerId: mark.marker_id, scores: [] }); + } + marksByMarkerAndPaper.get(key).scores.push({ + rubricCategoryId: mark.criterion_id, + score: parseFloat(mark.score) + }); + }); + + // Add the grouped marks to the correct control paper object. + marksByMarkerAndPaper.forEach((value, key) => { + const [markerId, paperId] = key.split('|'); + if (controlPapersMap.has(paperId)) { + controlPapersMap.get(paperId).marks.push(value); + } + }); + + // B) Construct the final response object in the exact shape the frontend expects. + const finalResponse = { + assignmentDetails: assignmentRes.rows[0], // This object now includes the `created_by` field. + currentUser: { + id: currentUserId, + role: currentUserRole, // This now includes the user's team-specific role. + personalComplete: personalStatusRes.rows[0]?.completed || false + }, + markers: markersRes.rows.map(marker => ({ + id: marker.id, + name: marker.username + })), + rubric: rubricWithTiers, + controlPapers: Array.from(controlPapersMap.values()), + markersAlreadyMarked: parseInt(markersAlreadyMarkedRes.rows[0].graded_marker_count, 10) + }; + + // --- STEP 4: SEND THE RESPONSE --- + res.json(finalResponse); + + } catch (err) { + console.error(`Failed to fetch detailed data for assignment ${assignmentId}:`, err); + res.status(500).json({ message: "Server error while fetching assignment details." }); + } +}); + +// Receives and saves the marks for a single control paper from a single marker. +// Uses a transaction to safely replace old marks with the new submission. +app.post("/assignments/:assignmentId/mark", authenticateToken, async (req, res) => { + // Use a database client from the pool to run a transaction + const client = await pool.connect(); + + try { + // --- STEP 1: EXTRACT DATA & VALIDATE --- + const { assignmentId } = req.params; + const { paperId, scores } = req.body; // 'paperId' is 'cp-A', 'cp-B', etc. + const tutorId = req.user.id; // The ID of the marker submitting the scores + + // Basic validation to ensure the payload is correct + if (!paperId || !scores || !Array.isArray(scores) || scores.length === 0) { + return res.status(400).json({ message: "Invalid payload. 'paperId' and a non-empty 'scores' array are required." }); + } + + // --- STEP 2: BEGIN DATABASE TRANSACTION --- + await client.query('BEGIN'); + + // --- STEP 3: FIND THE SUBMISSION ID --- + // We need to translate the 'paperId' (e.g., 'cp-A') into the actual ID + // from the 'submissions' table to use as a foreign key. + const submissionRes = await client.query( + `SELECT id FROM submissions WHERE assignment_id = $1 AND student_identifier = $2 AND is_control_paper = TRUE`, + [assignmentId, paperId] + ); + + if (submissionRes.rows.length === 0) { + // This is a server-side issue if the control paper doesn't exist. + // We throw an error to trigger the ROLLBACK. + throw new Error(`Control paper '${paperId}' not found for assignment ${assignmentId}.`); + } + const submissionId = submissionRes.rows[0].id; + + // --- STEP 4: DELETE OLD MARKS (for idempotency) --- + // To handle re-submissions, we first delete any existing marks this tutor + // may have already submitted for this specific control paper. + await client.query( + `DELETE FROM marks WHERE submission_id = $1 AND tutor_id = $2`, + [submissionId, tutorId] + ); + + // --- STEP 5: INSERT NEW MARKS --- + // Loop through the scores from the payload and insert each one as a new row. + const insertSql = ` + INSERT INTO marks (submission_id, criterion_id, tutor_id, marks_awarded) + VALUES ($1, $2, $3, $4) + `; + + for (const score of scores) { + // Add validation for each score object if desired + const values = [submissionId, score.criterionId, tutorId, score.score]; + await client.query(insertSql, values); + } + + await client.query(// Mark this tutor as completed in assignment_markers + `UPDATE assignment_markers + SET completed = TRUE + WHERE assignment_id = $1 AND user_id = $2`, + [assignmentId, tutorId] + ); + + + + // --- STEP 6: MARK STATUS AS COMPLETED --- + const totalRes = await client.query( + `SELECT COUNT(*) AS total FROM assignment_markers WHERE assignment_id = $1`, + [assignmentId] + ); + const completedRes = await client.query( + `SELECT COUNT(*) AS completed FROM assignment_markers WHERE assignment_id = $1 AND completed = TRUE`, + [assignmentId] + ); + + const total = parseInt(totalRes.rows[0].total, 10); + const completed = parseInt(completedRes.rows[0].completed, 10); + + let assignmentStatus = "Marking"; + if (completed === total) { + assignmentStatus = "Completed"; + await client.query( + `UPDATE assignments SET status = $1 WHERE id = $2`, + [assignmentStatus, assignmentId] + ); + } + + // --- Step 7: Return response to frontend --- + res.status(201).json({ + message: `Marks for ${paperId} submitted successfully!`, + myCompleted: true, + status: assignmentStatus + }); + + await client.query("COMMIT"); + + + + } catch (error) { + // If any error occurred in the 'try' block, undo all database changes. + await client.query('ROLLBACK'); + console.error("Error submitting marks:", error); + res.status(500).json({ message: "Failed to submit marks due to a server error." }); + } finally { + // ALWAYS release the client back to the pool. + client.release(); + } +}); + +// ========================== +// GET /team/:teamId/markers +// ========================== +app.get("/team/:teamId/markers", authenticateToken, async (req, res) => { + const { teamId } = req.params; + + try { + const result = await pool.query(` + SELECT + tm.id AS team_member_id, + u.id AS user_id, + u.username, + tm.role, + tm.joined_at + FROM team_members tm + INNER JOIN users u ON tm.user_id = u.id + WHERE tm.team_id = $1 + ORDER BY tm.joined_at ASC; + `, [teamId]); + + res.json(result.rows); + } catch (err) { + console.error("Error fetching team markers:", err); + res.status(500).json({ message: "Failed to fetch markers." }); + } +}); + +/////////////////////////////////// +// Team Dashboard used +/////////////////////////////////// +// Get team statistics for dashboard +// P.S. I never thought Dashboard integration would be this complicated and time-consuming +app.get("/team/:teamId/stats", authenticateToken, async (req, res) => { + const { teamId } = req.params; + const currentUserId = req.user.id; + + try { + // Check if the user is a member of the team + const memberCheck = await pool.query( + `SELECT role FROM team_members WHERE team_id = $1 AND user_id = $2`, + [teamId, currentUserId] + ); + + if (memberCheck.rows.length === 0) { + return res.status(403).json({ error: "Access denied: You are not a member of this team" }); + } + + const userRole = memberCheck.rows[0].role; + + // Based on role, adjust the queries accordingly + let assignmentsCondition = "a.team_id = $1"; + let queryParams = [teamId]; + + if (userRole !== 'admin') { + + assignmentsCondition = "a.team_id = $1 AND am.user_id = $2"; + queryParams.push(currentUserId); + } + + // Total Assignments + let assignmentsQuery = ` + SELECT COUNT(DISTINCT a.id) + FROM assignments a + ${userRole !== 'admin' ? 'INNER JOIN assignment_markers am ON a.id = am.assignment_id' : ''} + WHERE ${assignmentsCondition} + `; + const assignmentsRes = await pool.query(assignmentsQuery, queryParams); + + // Active Markers: number of unique markers who have submitted marks in the last 24 hours + const activeMarkersQuery = ` + SELECT COUNT(DISTINCT m.tutor_id) + FROM marks m + JOIN submissions s ON m.submission_id = s.id + JOIN assignments a ON s.assignment_id = a.id + WHERE a.team_id = $1 AND m.created_at >= NOW() - INTERVAL '24 hours' + `; + const activeMarkersRes = await pool.query(activeMarkersQuery, [teamId]); + + // Submissions Graded: total number of submissions that have been graded + const gradedRes = await pool.query( + `SELECT COUNT(*) + FROM marks m + JOIN submissions s ON m.submission_id = s.id + JOIN assignments a ON s.assignment_id = a.id + WHERE a.team_id = $1`, + [teamId] + ); + + // Flags Open: uncompleted markers across all assignments + const flagsOpenQuery = ` + SELECT COUNT(DISTINCT am.user_id) + FROM assignment_markers am + JOIN assignments a ON am.assignment_id = a.id + WHERE a.team_id = $1 AND am.completed = false + `; + const flagsOpenRes = await pool.query(flagsOpenQuery, [teamId]); + + // Total Team Members + const teamMembersRes = await pool.query( + `SELECT COUNT(*) FROM team_members WHERE team_id = $1`, + [teamId] + ); + + res.json({ + totalAssignments: parseInt(assignmentsRes.rows[0].count), + activeMarkers: parseInt(activeMarkersRes.rows[0].count), + submissionsGraded: parseInt(gradedRes.rows[0].count), + flagsOpen: parseInt(flagsOpenRes.rows[0].count), + totalTeamMembers: parseInt(teamMembersRes.rows[0].count) + }); + } catch (err) { + console.error("Error fetching team stats:", err); + res.status(500).json({ error: "Failed to fetch team statistics" }); + } +}); + + +// P.S. +// I know this is a big one and it looks scary and confusing, +// in fact, i used gen AI to assist me in writing it and I twisted it to fit the needs, +// even then it took me a while to understand it for myself, and I'll probably forget it after the handover meeting. +// And most imporantly, no one wants to write this part, the whole dashboard integration thing, i have to stay up late to do it, +// so don't judge me if anyone tries to read it or even understand it. :p +// also, if this website really has a future or +// this part of logic being used somewhere else, please refactor this endpoint +// add new databse schema or whatever you need to make it cleaner and easier to understand, +// because this is really a mess and I already hate mysef for writing this part. + +// Get recent assignments for dashboard +app.get("/team/:teamId/recent-assignments", authenticateToken, async (req, res) => { + const { teamId } = req.params; + const currentUserId = req.user.id; + + try { + //console.log("Fetching recent assignments for team:", teamId, "user:", currentUserId); + + // Check if the user is a member of the team + const memberCheck = await pool.query( + `SELECT role FROM team_members WHERE team_id = $1 AND user_id = $2`, + [teamId, currentUserId] + ); + + if (memberCheck.rows.length === 0) { + return res.status(403).json({ error: "Access denied: You are not a member of this team" }); + } + + const userRole = memberCheck.rows[0].role; + + // Based on role, adjust the queries accordingly + let assignmentsQuery; + let queryParams = [teamId]; + + if (userRole === 'admin') { + // Admins can see all assignments + assignmentsQuery = ` + SELECT + a.id, + a.course_code, + a.course_name, + a.due_date, + a.status, + a.created_by, + COUNT(DISTINCT am.user_id) as total_markers, + COUNT(DISTINCT CASE WHEN am.completed = true THEN am.user_id END) as completed_markers, + u.username as created_by_username, + COUNT(DISTINCT CASE WHEN am.completed = false THEN am.user_id END) as flags_count, + a.created_at + FROM assignments a + LEFT JOIN assignment_markers am ON a.id = am.assignment_id + LEFT JOIN users u ON a.created_by = u.id + WHERE a.team_id = $1 + GROUP BY a.id, u.username, a.created_at + ORDER BY a.created_at DESC + LIMIT 5 + `; + } else { + // Tutors can only see assignments assigned to them + assignmentsQuery = ` + SELECT + a.id, + a.course_code, + a.course_name, + a.due_date, + a.status, + a.created_by, + COUNT(DISTINCT am.user_id) as total_markers, + COUNT(DISTINCT CASE WHEN am.completed = true THEN am.user_id END) as completed_markers, + u.username as created_by_username, + am.completed as user_completed, + COUNT(DISTINCT CASE WHEN am.completed = false THEN am.user_id END) as flags_count, + a.created_at + FROM assignments a + INNER JOIN assignment_markers am ON a.id = am.assignment_id + LEFT JOIN users u ON a.created_by = u.id + WHERE a.team_id = $1 AND am.user_id = $2 + GROUP BY a.id, u.username, am.completed, a.created_at + ORDER BY a.created_at DESC + LIMIT 5 + `; + queryParams.push(currentUserId); + } + + //console.log("Executing query:", assignmentsQuery); + //console.log("With parameters:", queryParams); + + const assignmentsRes = await pool.query(assignmentsQuery, queryParams); + + //console.log("Database query result:", assignmentsRes.rows); + + // Light formatting of the assignments data + const assignments = assignmentsRes.rows.map(assignment => ({ + id: assignment.id, + course_code: assignment.course_code, + course_name: assignment.course_name, + due_date: assignment.due_date, + status: assignment.status, + created_by: assignment.created_by_username, + total_markers: parseInt(assignment.total_markers) || 0, + completed_markers: parseInt(assignment.completed_markers) || 0, + progress: assignment.total_markers > 0 + ? Math.round((assignment.completed_markers / assignment.total_markers) * 100) + : 0, + flags: parseInt(assignment.flags_count) || 0, + user_completed: assignment.user_completed || false + })); + + //console.log("Formatted assignments:", assignments); + + res.json({ + assignments, + userRole + }); + } catch (err) { + console.error("Error fetching recent assignments:", err); + res.status(500).json({ error: "Failed to fetch recent assignments" }); + } +}); + +// Get upcoming deadlines for dashboard +app.get("/team/:teamId/upcoming-deadlines", authenticateToken, async (req, res) => { + const { teamId } = req.params; + const currentUserId = req.user.id; + + try { + + // Same as before, check if the user is a member of the team + const memberCheck = await pool.query( + `SELECT role FROM team_members WHERE team_id = $1 AND user_id = $2`, + [teamId, currentUserId] + ); + + if (memberCheck.rows.length === 0) { + return res.status(403).json({ error: "Access denied: You are not a member of this team" }); + } + + const userRole = memberCheck.rows[0].role; + + // Same as before, adjust the query based on role + let deadlinesQuery; + let queryParams = [teamId]; + + if (userRole === 'admin') { + deadlinesQuery = ` + SELECT + a.id, + a.course_code, + a.course_name, + a.due_date, + a.status, + a.created_at, + u.username as created_by_username + FROM assignments a + LEFT JOIN users u ON a.created_by = u.id + WHERE a.team_id = $1 + AND a.due_date > NOW() + AND a.due_date <= NOW() + INTERVAL '7 days' + ORDER BY a.due_date ASC + LIMIT 5 + `; + } else { + deadlinesQuery = ` + SELECT + a.id, + a.course_code, + a.course_name, + a.due_date, + a.status, + a.created_at, + u.username as created_by_username + FROM assignments a + INNER JOIN assignment_markers am ON a.id = am.assignment_id + LEFT JOIN users u ON a.created_by = u.id + WHERE a.team_id = $1 + AND am.user_id = $2 + AND a.due_date > NOW() + AND a.due_date <= NOW() + INTERVAL '7 days' + ORDER BY a.due_date ASC + LIMIT 5 + `; + queryParams.push(currentUserId); + } + + + + const deadlinesRes = await pool.query(deadlinesQuery, queryParams); + + + // Light formatting of the deadlines data + const deadlines = deadlinesRes.rows.map(assignment => { + const dueDate = new Date(assignment.due_date); + const now = new Date(); + const diffTime = dueDate - now; + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + let dueIn; + if (diffDays === 0) { + dueIn = "Due today"; + } else if (diffDays === 1) { + dueIn = "Due tomorrow"; + } else { + dueIn = `Due in ${diffDays} days`; + } + + return { + id: assignment.id, + course_code: assignment.course_code, + course_name: assignment.course_name, + due_date: assignment.due_date, + status: assignment.status, + created_by: assignment.created_by_username, + due_in: dueIn, + last_updated: assignment.created_at + }; + }); + + + + res.json({ + deadlines + }); + } catch (err) { + console.error("Error fetching upcoming deadlines:", err); + res.status(500).json({ error: "Failed to fetch upcoming deadlines" }); + } +}); + +// Get chart data for dashboard +// P.S.I also used gen Ai to help me write this part, +// coz again, no one wants to do this, and it's late at night, i suppose it's fine as long as it works :p +app.get("/team/:teamId/chart-data", authenticateToken, async (req, res) => { + const { teamId } = req.params; + const currentUserId = req.user.id; + + try { + console.log("Fetching chart data for team:", teamId); + + const memberCheck = await pool.query( + `SELECT role FROM team_members WHERE team_id = $1 AND user_id = $2`, + [teamId, currentUserId] + ); + + if (memberCheck.rows.length === 0) { + return res.status(403).json({ error: "Access denied: You are not a member of this team" }); + } + + // Query to get submission counts per month for the last 6 months + const chartDataQuery = ` + SELECT + to_char(date_trunc('month', s.created_at), 'Mon') as month_short, + COUNT(*) as total + FROM submissions s + JOIN assignments a ON s.assignment_id = a.id + WHERE a.team_id = $1 + AND s.created_at >= date_trunc('month', current_date - interval '5 months') + GROUP BY date_trunc('month', s.created_at) + ORDER BY date_trunc('month', s.created_at) + `; + + const chartDataRes = await pool.query(chartDataQuery, [teamId]); + + console.log("Raw chart data from DB:", chartDataRes.rows); + + // If empty result, return mock data + if (chartDataRes.rows.length === 0) { + console.log("No data found, returning mock data"); + const mockData = [ + { month: "Jan", total: 22 }, + { month: "Feb", total: 22 }, + { month: "Mar", total: 50 }, + { month: "Apr", total: 22 }, + { month: "May", total: 10 }, + { month: "Jun", total: 25 }, + ]; + return res.json({ chartData: mockData }); + } + + // Format the data for the frontend + const chartData = chartDataRes.rows.map(row => ({ + month: row.month_short, + total: parseInt(row.total) + })); + + console.log("Formatted chart data:", chartData); + + res.json({ + chartData + }); + } catch (err) { + console.error("Error fetching chart data:", err); + // Use mock data on error as well + const mockData = [ + { month: "Jan", total: 12 }, + { month: "Feb", total: 18 }, + { month: "Mar", total: 15 }, + { month: "Apr", total: 22 }, + { month: "May", total: 19 }, + { month: "Jun", total: 25 }, + ]; + res.json({ chartData: mockData }); + } +}); + +////////////////////////////////////// +/// Marker Stats +////////////////////////////////////// + +// Get marker statistics for dashboard +app.get("/team/:teamId/marker-stats", authenticateToken, async (req, res) => { + const { teamId } = req.params; + const currentUserId = req.user.id; + + try { + // Vlidate team membership + const memberCheck = await pool.query( + `SELECT role FROM team_members WHERE team_id = $1 AND user_id = $2`, + [teamId, currentUserId] + ); + + if (memberCheck.rows.length === 0) { + return res.status(403).json({ error: "Access denied: You are not a member of this team" }); + } + + // Get detailed stats for each marker in the team + const markerStatsQuery = ` + SELECT + tm.user_id, + u.username, + tm.role, + tm.joined_at, + COUNT(DISTINCT CASE WHEN am.completed = true THEN am.assignment_id END) as completed_assignments, + COUNT(DISTINCT am.assignment_id) as total_assignments, + CASE + WHEN COUNT(DISTINCT am.assignment_id) > 0 THEN + ROUND((COUNT(DISTINCT CASE WHEN am.completed = true THEN am.assignment_id END) * 100.0 / COUNT(DISTINCT am.assignment_id))::numeric, 0) + ELSE 0 + END as completion_percentage, + COUNT(DISTINCT CASE WHEN am.completed = false THEN am.assignment_id END) as pending_assignments, + 0 as average_deviation + FROM team_members tm + INNER JOIN users u ON tm.user_id = u.id + LEFT JOIN assignment_markers am ON tm.user_id = am.user_id + LEFT JOIN assignments a ON am.assignment_id = a.id AND a.team_id = tm.team_id + WHERE tm.team_id = $1 + GROUP BY tm.user_id, u.username, tm.role, tm.joined_at + ORDER BY tm.joined_at ASC + `; + + const markerStatsRes = await pool.query(markerStatsQuery, [teamId]); + + // Format the marker statistics data + const markerStats = markerStatsRes.rows.map(marker => ({ + user_id: marker.user_id, + username: marker.username, + role: marker.role, + joined_at: marker.joined_at, + completed_assignments: parseInt(marker.completed_assignments) || 0, + total_assignments: parseInt(marker.total_assignments) || 0, + completion_percentage: parseInt(marker.completion_percentage) || 0, + pending_assignments: parseInt(marker.pending_assignments) || 0 + //average_deviation: parseFloat(marker.average_deviation) || 0 + })); + + res.json({ + markerStats + }); + } catch (err) { + console.error("Error fetching marker stats:", err); + res.status(500).json({ error: "Failed to fetch marker statistics" }); + } +}); + + +/////////////////////////////////// +/////Remove Marker from Team +/////////////////////////////////// +// Delete a team member and all their associated data +app.delete("/team/:teamId/markers/:userId", authenticateToken, async (req, res) => { + const { teamId, userId } = req.params; + const currentUserId = req.user.id; + + try { + // Check if the current user is an admin of the team + const memberCheck = await pool.query( + `SELECT role FROM team_members WHERE team_id = $1 AND user_id = $2`, + [teamId, currentUserId] + ); + + if (memberCheck.rows.length === 0) { + return res.status(403).json({ error: "Access denied: You are not a member of this team" }); + } + + if (memberCheck.rows[0].role !== 'admin') { + return res.status(403).json({ error: "Access denied: Only admins can remove team members" }); + } + + // In prevent admins from removing themselves + if (parseInt(userId) === currentUserId) { + return res.status(400).json({ error: "You cannot remove yourself from the team" }); + } + + // Check if the user to be removed is the team owner + const teamOwnerCheck = await pool.query( + `SELECT owner_id FROM teams WHERE id = $1`, + [teamId] + ); + + if (teamOwnerCheck.rows.length > 0 && parseInt(userId) === teamOwnerCheck.rows[0].owner_id) { + return res.status(400).json({ error: "Cannot remove the team owner" }); + } + + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + // 1. Delete all assignment marker entries for this user in this team + await client.query( + `DELETE FROM assignment_markers WHERE user_id = $1 AND assignment_id IN ( + SELECT id FROM assignments WHERE team_id = $2 + )`, + [userId, teamId] + ); + + // 2. Delete all marks given by this user for submissions in this team + await client.query( + `DELETE FROM marks WHERE tutor_id = $1 AND submission_id IN ( + SELECT s.id FROM submissions s + JOIN assignments a ON s.assignment_id = a.id + WHERE a.team_id = $2 + )`, + [userId, teamId] + ); + + // 3. Delete the user from the team_members table + const deleteResult = await client.query( + `DELETE FROM team_members WHERE team_id = $1 AND user_id = $2`, + [teamId, userId] + ); + + if (deleteResult.rowCount === 0) { + await client.query('ROLLBACK'); + return res.status(404).json({ error: "Team member not found" }); + } + + await client.query('COMMIT'); + res.json({ message: "Team member removed successfully" }); + } catch (err) { + await client.query('ROLLBACK'); + throw err; + } finally { + client.release(); + } + } catch (err) { + console.error("Error removing team member:", err); + res.status(500).json({ error: "Failed to remove team member" }); + } +}); + + ///////////////////// // Start Server ///////////////////// diff --git a/backend/temp/AIP_Assignment_2_-_Copy.docx b/backend/temp/AIP_Assignment_2_-_Copy.docx new file mode 100644 index 0000000..e69de29 diff --git a/backend/temp/temp.txt b/backend/temp/temp.txt new file mode 100644 index 0000000..e69de29 diff --git a/backend/users.db b/backend/users.db new file mode 100644 index 0000000..f1b787c Binary files /dev/null and b/backend/users.db differ diff --git a/frontend-vite/.env.example b/frontend-vite/.env.example new file mode 100644 index 0000000..9b4e331 --- /dev/null +++ b/frontend-vite/.env.example @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:5000 \ No newline at end of file diff --git a/frontend-vite/jsconfig.json b/frontend-vite/jsconfig.json index cbc1d68..0061394 100644 --- a/frontend-vite/jsconfig.json +++ b/frontend-vite/jsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "baseUrl": ".", + "ignoreDeprecations": "6.0", "paths": { "@/*": ["./src/*"] } diff --git a/frontend-vite/package-lock.json b/frontend-vite/package-lock.json index 375b76e..9210ff2 100644 --- a/frontend-vite/package-lock.json +++ b/frontend-vite/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.13", "axios": "^1.11.0", @@ -19,12 +20,18 @@ "date-fns": "^4.1.0", "jwt-decode": "^4.0.0", "lucide-react": "^0.540.0", + "pdfjs-dist": "^4.4.168", "react": "^19.1.1", - "react-day-picker": "^9.9.0", + "react-day-picker": "^9.11.0", "react-dom": "^19.1.1", + "react-loading-skeleton": "^3.5.0", + "react-pdf": "^9.1.0", + "react-resizable-panels": "^3.0.6", "react-router-dom": "^7.8.1", + "recharts": "^3.2.1", "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.13" + "tailwindcss": "^4.1.13", + "xlsx": "^0.18.5" }, "devDependencies": { "@eslint/js": "^9.33.0", @@ -40,7 +47,8 @@ "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", "tw-animate-css": "^1.3.7", - "vite": "^7.1.2" + "vite": "^7.1.2", + "vite-plugin-static-copy": "^3.1.3" } }, "node_modules/@adobe/css-tools": { @@ -1093,6 +1101,125 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -1400,6 +1527,30 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -1545,6 +1696,32 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@reduxjs/toolkit": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", + "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.32", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz", @@ -1812,6 +1989,18 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", @@ -2217,6 +2406,69 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2260,6 +2512,12 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.1.tgz", @@ -2281,6 +2539,13 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2304,6 +2569,28 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2325,9 +2612,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -2348,6 +2634,55 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2384,9 +2719,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2403,20 +2738,46 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.25.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", @@ -2494,6 +2855,35 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2511,6 +2901,44 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2541,6 +2969,15 @@ "node": ">=6" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2561,6 +2998,16 @@ "dev": true, "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2577,9 +3024,16 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2596,8 +3050,20 @@ "node": ">=18" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, @@ -2625,6 +3091,127 @@ "devOptional": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/date-fns": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", @@ -2645,7 +3232,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2659,6 +3246,25 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2675,11 +3281,17 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2729,6 +3341,13 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -2787,6 +3406,16 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.39.10", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", + "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -3029,6 +3658,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3080,6 +3715,19 @@ "node": ">=16.0.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3154,6 +3802,70 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3177,6 +3889,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3233,6 +3967,28 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3314,6 +4070,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3326,6 +4089,20 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3336,6 +4113,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz", + "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3373,6 +4160,47 @@ "node": ">=8" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3383,6 +4211,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3396,6 +4234,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3416,7 +4264,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -3479,6 +4326,19 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -3763,6 +4623,18 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3802,22 +4674,73 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/make-cancellable-promise": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.3.2.tgz", + "integrity": "sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/wojtekmaj/make-cancellable-promise?sponsor=1" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-event-props": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.6.2.tgz", + "integrity": "sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-refs": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.3.0.tgz", + "integrity": "sha512-nqXPXbso+1dcKDpPCXvwZyJILz+vSLqGGOnDrYHQYE+B8n9JTCekVLC65AfCpR4ggVyA/45Y0iR9LDyS2iI+zA==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/merge-refs?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, "node_modules/mime-types": { @@ -3832,6 +4755,19 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3846,7 +4782,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3895,9 +4831,16 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -3923,6 +4866,27 @@ "dev": true, "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -3930,6 +4894,66 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "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", + "optional": true, + "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", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3980,6 +5004,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4003,6 +5040,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4013,6 +5060,29 @@ "node": ">=8" } }, + "node_modules/path2d": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.2.tgz", + "integrity": "sha512-+vnG6S4dYcYxZd+CZxzXCNKdELYZSKfohrk98yajCo1PtRoDgCTrrwOvK1GT0UoAdVszagDVllQc0U1vaX4NUQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pdfjs-dist": { + "version": "4.4.168", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.4.168.tgz", + "integrity": "sha512-MbkAjpwka/dMHaCfQ75RY1FXX3IewBVu6NGZOcxerRFlaBiIkZmUoR0jotX5VUzYZEXAGzSFtknWs5xRKliXPA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d": "^0.2.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4125,9 +5195,9 @@ } }, "node_modules/react-day-picker": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.9.0.tgz", - "integrity": "sha512-NtkJbuX6cl/VaGNb3sVVhmMA6LSMnL5G3xNL+61IyoZj0mUZFWTg4hmj7PHjIQ8MXN9dHWhUHFoJWG6y60DKSg==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.0.tgz", + "integrity": "sha512-L4FYOaPrr3+AEROeP6IG2mCORZZfxJDkJI2df8mv1jyPrNYeccgmFPZDaHyAuPCBCddQFozkxbikj2NhMEYfDQ==", "license": "MIT", "dependencies": { "@date-fns/tz": "^1.4.1", @@ -4161,10 +5231,70 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, "license": "MIT", "peer": true }, + "node_modules/react-loading-skeleton": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz", + "integrity": "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/react-pdf": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.1.0.tgz", + "integrity": "sha512-KhPDQE3QshkLdS3b48S5Bldv0N5flob6qwvsiADWdZOS5TMDaIrkRtEs+Dyl6ubRf2jTf9jWmFb6RjWu46lSSg==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "dequal": "^2.0.3", + "make-cancellable-promise": "^1.3.1", + "make-event-props": "^1.6.0", + "merge-refs": "^1.3.0", + "pdfjs-dist": "4.4.168", + "tiny-invariant": "^1.0.0", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-pdf?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -4222,6 +5352,16 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.6.tgz", + "integrity": "sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/react-router": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.1.tgz", @@ -4282,6 +5422,74 @@ } } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/recharts": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz", + "integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==", + "license": "MIT", + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -4296,6 +5504,27 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4306,6 +5535,23 @@ "node": ">=4" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.46.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.4.tgz", @@ -4345,6 +5591,27 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -4355,12 +5622,19 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -4390,6 +5664,46 @@ "node": ">=8" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4399,6 +5713,56 @@ "node": ">=0.10.0" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -4493,14 +5857,20 @@ "node": ">=18" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -4509,6 +5879,26 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4545,6 +5935,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -4629,10 +6029,48 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", - "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -4640,7 +6078,7 @@ "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -4703,6 +6141,53 @@ } } }, + "node_modules/vite-plugin-static-copy": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.3.tgz", + "integrity": "sha512-U47jgyoJfrvreF87u2udU6dHIXbHhdgGZ7wSEqn6nVHKDOMdRoB2uVc6iqxbEzENN5JvX6djE5cBhQZ2MMBclA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "fs-extra": "^11.3.2", + "p-map": "^7.0.3", + "picocolors": "^1.1.1", + "tinyglobby": "^0.2.15" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4719,6 +6204,34 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -4729,6 +6242,34 @@ "node": ">=0.10.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", + "optional": true + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend-vite/package.json b/frontend-vite/package.json index d096c6f..0a98f0a 100644 --- a/frontend-vite/package.json +++ b/frontend-vite/package.json @@ -13,6 +13,7 @@ "dependencies": { "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.13", "axios": "^1.11.0", @@ -22,12 +23,18 @@ "date-fns": "^4.1.0", "jwt-decode": "^4.0.0", "lucide-react": "^0.540.0", + "pdfjs-dist": "^4.4.168", "react": "^19.1.1", - "react-day-picker": "^9.9.0", + "react-day-picker": "^9.11.0", "react-dom": "^19.1.1", + "react-loading-skeleton": "^3.5.0", + "react-pdf": "^9.1.0", + "react-resizable-panels": "^3.0.6", "react-router-dom": "^7.8.1", + "recharts": "^3.2.1", "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.13" + "tailwindcss": "^4.1.13", + "xlsx": "^0.18.5" }, "devDependencies": { "@eslint/js": "^9.33.0", @@ -43,6 +50,7 @@ "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", "tw-animate-css": "^1.3.7", - "vite": "^7.1.2" + "vite": "^7.1.2", + "vite-plugin-static-copy": "^3.1.3" } } diff --git a/frontend-vite/public/Logo.png b/frontend-vite/public/Logo.png new file mode 100644 index 0000000..e80f9b1 Binary files /dev/null and b/frontend-vite/public/Logo.png differ diff --git a/frontend-vite/public/UserTips.txt b/frontend-vite/public/UserTips.txt new file mode 100644 index 0000000..c7d1c18 --- /dev/null +++ b/frontend-vite/public/UserTips.txt @@ -0,0 +1,45 @@ +***Dashboard*** + +The standard of active user is if the user submits a marking in 24 hours. + +The meaning of flags open is how many markers haven't finished marking. + +Upcoming deadline indicates assignments due within 7 days. + +***New Assignment*** + +Please upload control papers in PDF or DOCX format only. + While DOC files might work, they can cause formatting glitches. + +For best results, we recommend uploading PDF files. + All files are stored in PDF format, and any DOCX file uploaded will be converted automatically — this may slightly change the layout, especially if the document includes tables or charts. + +***Assignments Page*** + +The status meaning differs between roles: + + Admin: Status shows whether all markers have completed marking. + + Tutor: Status shows whether you personally have finished marking. + +Only admins can delete assignments, so the delete button will not appear for tutors. + +You can use the search bar to find assignments by course name or course code. + +***Teams*** + +The person who creates a team automatically becomes its admin. + +You can invite others to join your team via the Markers page. + +Invited members are automatically assigned the tutor role. + +***Reports*** + +Only admins can access reports. + +On reports page, admins can leave comments to rubric. + +For completed assignments, admins can export it as PDF. + + diff --git a/frontend-vite/public/broken-file-icon.png b/frontend-vite/public/broken-file-icon.png new file mode 100644 index 0000000..d9151bc Binary files /dev/null and b/frontend-vite/public/broken-file-icon.png differ diff --git a/frontend-vite/public/logo_black.png b/frontend-vite/public/logo_black.png new file mode 100644 index 0000000..3bedbf0 Binary files /dev/null and b/frontend-vite/public/logo_black.png differ diff --git a/frontend-vite/src/App.css b/frontend-vite/src/App.css index 1a06fad..7cd4b0d 100644 --- a/frontend-vite/src/App.css +++ b/frontend-vite/src/App.css @@ -47,6 +47,7 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + --text-2xs: 0.625rem; --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); diff --git a/frontend-vite/src/App.jsx b/frontend-vite/src/App.jsx index 07e9f96..68c9310 100644 --- a/frontend-vite/src/App.jsx +++ b/frontend-vite/src/App.jsx @@ -9,13 +9,21 @@ import CreateAssignment from "./pages/CreateAssignment"; import InviteMarkers from "./pages/InviteMarkers"; import JoinTeam from "./pages/JoinTeam"; import Assignments from "./pages/Assignments"; +import AssignmentDetails from "./pages/AssignmentDetails"; +import MarkingPage from "./pages/MarkingPage"; +import IndividualDashboard from "./pages/IndividualDashboard"; +import AssignmentMakers from "./pages/AssignmentMakers"; +import ReportsDetails from "./pages/ReportsDetails"; +import Setting from "./pages/Setting"; +import Markers from "./pages/Markers"; +import Reports from "./pages/Reports"; import api from "./utils/axios"; import { jwtDecode } from "jwt-decode"; function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [isLoading, setIsLoading] = useState(true); - const [userTeams, setUserTeams] = useState([]); // stores teams for redirecting + const [userTeams, setUserTeams] = useState([]); const getAuthStatus = () => { const token = localStorage.getItem("token"); @@ -34,7 +42,6 @@ function App() { } }; - // fetch user's teams const fetchTeams = async () => { const token = localStorage.getItem("token"); if (!token) return []; @@ -54,8 +61,13 @@ function App() { setIsLoggedIn(authStatus); if (authStatus) { - const teams = await fetchTeams(); - setUserTeams(teams); + try { + const teams = await fetchTeams(); + setUserTeams(teams); + } catch (error) { + console.error("Failed to fetch teams:", error); + setUserTeams([]); + } } setIsLoading(false); @@ -63,7 +75,11 @@ function App() { checkAuth(); - const handleStorageChange = () => checkAuth(); + const handleStorageChange = () => { + setIsLoading(true); + checkAuth(); + }; + window.addEventListener("storage", handleStorageChange); window.addEventListener("authChange", handleStorageChange); @@ -73,70 +89,93 @@ function App() { }; }, []); - // New component to handle the logic for the join-team route const JoinTeamRoute = () => { const location = useLocation(); - const token = new URLSearchParams(location.search).get('token'); + const searchParams = new URLSearchParams(location.search); + const token = searchParams.get('token'); + + console.log('JoinTeamRoute - Token from URL:', token); + console.log('JoinTeamRoute - User logged in:', isLoggedIn); if (isLoggedIn) { - // If user is logged in, they can see the page. - // We should also clear the pending token now that they've reached the page. sessionStorage.removeItem("pendingInviteToken"); return ; } - // If user is not logged in, save the token and redirect to login. + // If not logged in, save the token and redirect to login if (token) { + console.log('Saving pending invite token:', token); sessionStorage.setItem('pendingInviteToken', token); + + // Redirect to login with redirect back to join-team + const redirectPath = `/join-team?token=${token}`; + return ; } + // If no token is present, redirect to login return ; }; + // Redirect logged-in users appropriately + const getLoggedInRedirect = () => { + const pendingInviteToken = sessionStorage.getItem("pendingInviteToken"); + + if (pendingInviteToken) { + console.log('Redirecting to join-team with token:', pendingInviteToken); + sessionStorage.removeItem("pendingInviteToken"); + return ; + } + + return userTeams.length > 0 + ? + : ; + }; if (isLoading) return
Loading...
; - const pendingInviteToken = sessionStorage.getItem("pendingInviteToken"); - - const loggedInRedirect = pendingInviteToken - ? - : (userTeams.length > 0 ? : ); - return ( - - - 0 ? : ) - : - } - /> - - } - /> - - } - /> - - } - /> - - } /> - } /> - } /> - } /> - } /> - } /> - + + 0 ? : ) + : + } + /> + + } + /> + + } + /> + + } + /> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + ); } diff --git a/frontend-vite/src/components/AreaChartComponent.jsx b/frontend-vite/src/components/AreaChartComponent.jsx new file mode 100644 index 0000000..d537a5a --- /dev/null +++ b/frontend-vite/src/components/AreaChartComponent.jsx @@ -0,0 +1,122 @@ +import React, { useState, useEffect } from 'react'; +import { + Area, + AreaChart, + CartesianGrid, + XAxis, + YAxis, + ResponsiveContainer, + Tooltip, +} from "recharts"; +import api from "../utils/axios"; + +const mockData = [ + { month: "Jan", total: 12 }, + { month: "Feb", total: 18 }, + { month: "Mar", total: 15 }, + { month: "Apr", total: 22 }, + { month: "May", total: 19 }, + { month: "Jun", total: 25 }, +]; + +const AreaChartComponent = ({ teamId,}) => { + const [chartData, setChartData] = useState(mockData); + const [isLoading, setIsLoading] = useState(true); + + + + useEffect(() => { + const fetchChartData = async () => { + setIsLoading(true); + try { + const token = localStorage.getItem("token"); + console.log("Fetching chart data for team:", teamId); + + const res = await api.get(`/team/${teamId}/chart-data`, { + headers: { Authorization: `Bearer ${token}` } + }); + + console.log("API response:", res.data); + + if (res.data && res.data.chartData) { + console.log("Chart data received"); + setChartData(res.data.chartData); + } else { + console.log("No chart data received, using mock data"); + setChartData(mockData); + } + } catch (err) { + console.error("Failed to fetch chart data:", err); + setChartData(mockData); + } finally { + setIsLoading(false); + } + }; + + if (teamId) { + fetchChartData(); + } else { + setChartData(mockData); + setIsLoading(false); + } + }, [teamId]); + + if (isLoading) { + return ( +
+
Loading chart data...
+
+ ); + } + + return ( +
+ + + + + + [`${value} submissions`, 'Total']} + labelFormatter={(label) => `Month: ${label}`} + /> + + + +
+ ); +}; + +export default AreaChartComponent; \ No newline at end of file diff --git a/frontend-vite/src/components/AssignmentCard.jsx b/frontend-vite/src/components/AssignmentCard.jsx new file mode 100644 index 0000000..1d52680 --- /dev/null +++ b/frontend-vite/src/components/AssignmentCard.jsx @@ -0,0 +1,73 @@ +export default function AssignmentCard({ assignment, onNavigate, onDelete }) { + const getStatusColor = (status) => { + switch (status.toLowerCase()) { + case 'complete': + return 'bg-[var(--deakinTeal)] text-white'; + case 'marking': + return 'bg-[var(--lime)] text-gray-800'; + case 'moderating': + return 'bg-[var(--purple)] text-white'; + default: + return 'bg-gray-100 text-gray-800'; + } + }; + + return ( +
onNavigate(`assignments/${assignment.id}`)} + className="w-full bg-white rounded-lg outline outline-1 outline-offset-[-1px] outline-slate-200 px-6 pt-3.5 pb-4 hover:shadow-md transition cursor-pointer inline-flex flex-col justify-start items-start gap-1.5" + > + {/* 'semester' from the API */} +
+
+ Semester {assignment.semester} +
+
+
{assignment.status.charAt(0) + assignment.status.slice(1).toLowerCase()}
+
+
+ + + {/* Using 'course_name' instead of 'title' */} +
+
{assignment.course_name}
+
+ + {/* Using 'course_code' from the API */} +
+ {assignment.course_code} +
+ +
+ {assignment.markersAlreadyMarked || 0} / {assignment.markers?.length || 0 } graded + ({((assignment.markersAlreadyMarked || 0) / (assignment.markers?.length || 0) * 100).toFixed(0)}%) +
+ +
{/* Pushes due date to the bottom */} + +
+ {/* Formatting the due date for better readability */} +
+ Due: {new Date(assignment.due_date).toLocaleDateString()} +
+
+ {/* export button, placeholder for now */} + + {/* Delete button outside the clickable area */} + +
+
+
+ ); +} diff --git a/frontend-vite/src/components/AssignmentRow.jsx b/frontend-vite/src/components/AssignmentRow.jsx new file mode 100644 index 0000000..902b534 --- /dev/null +++ b/frontend-vite/src/components/AssignmentRow.jsx @@ -0,0 +1,40 @@ +import React from "react"; +import { Label } from "@/components/ui/label"; +import { Progress } from "@/components/ui/progress"; + +export default function AssignmentRow({ + title, + updatedText, + labelText, + completedText, + percentText, + progressValue, + flagsText, + onViewDetails, +}) { + return ( +
+
+
{title}
+
{updatedText}
+
+ +
+
+
{completedText}
+
{percentText}
+
+ +
+
{flagsText}
+ +
+ ); +} + + diff --git a/frontend-vite/src/components/DashboardCard.jsx b/frontend-vite/src/components/DashboardCard.jsx new file mode 100644 index 0000000..27e92d5 --- /dev/null +++ b/frontend-vite/src/components/DashboardCard.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const DashboardCard = ({ + title, + value, + className = "" +}) => { + return ( +
+
{value}
+
{title}
+
+ ); +}; + +export default DashboardCard; diff --git a/frontend-vite/src/components/DeadlineCard.jsx b/frontend-vite/src/components/DeadlineCard.jsx new file mode 100644 index 0000000..265f5c7 --- /dev/null +++ b/frontend-vite/src/components/DeadlineCard.jsx @@ -0,0 +1,50 @@ +import React from 'react'; + +const DeadlineCard = ({ + dueIn, + title, + lastUpdated, + onClick, + assignmentId +}) => { + + // Format last updated string + const formatLastUpdated = (dateString) => { + if (!dateString) return "Recently updated"; + + const date = new Date(dateString); + const now = new Date(); + const diffMs = now - date; + const diffMins = Math.floor(diffMs / (1000 * 60)); + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffMins < 60) { + return `Updated ${diffMins} min ago`; + } else if (diffHours < 24) { + return `Updated ${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; + } else { + return `Updated ${diffDays} day${diffDays > 1 ? 's' : ''} ago`; + } + }; + + return ( +
onClick(assignmentId)} // Send assignmentId on click + > +
+
{dueIn}
+
{title}
+
{formatLastUpdated(lastUpdated)}
+
+
+ + + +
+
+ ); +}; + +export default DeadlineCard; \ No newline at end of file diff --git a/frontend-vite/src/components/LoadingSpinner.jsx b/frontend-vite/src/components/LoadingSpinner.jsx new file mode 100644 index 0000000..727ca45 --- /dev/null +++ b/frontend-vite/src/components/LoadingSpinner.jsx @@ -0,0 +1,29 @@ +export default function LoadingSpinner({ pageName = "page" }) { + return ( +
+
+ + Loading... +
+

Loading {pageName} page

+
+ ); +} + diff --git a/frontend-vite/src/components/MarkerCard.jsx b/frontend-vite/src/components/MarkerCard.jsx new file mode 100644 index 0000000..8418cd1 --- /dev/null +++ b/frontend-vite/src/components/MarkerCard.jsx @@ -0,0 +1,90 @@ +import React from "react"; + +const MarkerCard = ({ + id, + name, + status, + gradedCount, + totalCount, + percentage, + deviation, + flagsRaised, + onRemove, + onSendEmail +}) => { + const getStatusColor = (status) => { + switch (status.toLowerCase()) { + case "marking": + return "bg-[var(--deakinTeal)] text-white"; + case "complete": + return "bg-[var(--lime)] text-gray-800"; + case "moderating": + return "bg-[var(--purple)] text-white"; + default: + return "bg-gray-100 text-gray-800"; + } + }; + + return ( +
+ {/* Header with ID and Status */} +
+
+ {id} +
+
+
{status.charAt(0) + status.slice(1).toLowerCase()}
+
+
+ + {/* Profile and Details */} +
+ {/* Profile Avatar */} +
+ + {/* Marker Details */} +
+

+ {name} +

+
+ {gradedCount} / {totalCount} graded ({percentage}%) +
+
+ Deviation: {deviation}↑ +
+
+
+ + {/* Footer with Flags and Actions */} +
+
+ {flagsRaised} flags Raised +
+
+ {/* */} + +
+
+
+ ); +}; + +export default MarkerCard; diff --git a/frontend-vite/src/components/Navbar.jsx b/frontend-vite/src/components/Navbar.jsx index a2703ef..5b33e68 100644 --- a/frontend-vite/src/components/Navbar.jsx +++ b/frontend-vite/src/components/Navbar.jsx @@ -39,17 +39,6 @@ export default function NavBar({onBurgerClick}) {
{/* Menu Button */}
-
{/*
*/} { + const onKey = (e) => { + if (e.key === "Escape") setMenuOpen(false); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [] + ); + + useEffect(() => { + const userStr = localStorage.getItem("user"); + if (userStr) { + const user = JSON.parse(userStr); + console.log(user.username); + console.log(user.id); + setUsername(user.username); + } + }, []) + + return ( +
+ {/* Top bar */} +
+
+ {/* Menu Button */} +
+ {/* */} +
+ {/*
*/} + User Avatar +
+ + {username ? username.charAt(0).toUpperCase() : ""} + +
+
+ + {username} + +
+ + {/* Search */} +
+
+ Menu Icon + +
+
+
+
+
+ ); +} diff --git a/frontend-vite/src/components/NavbarMenu.jsx b/frontend-vite/src/components/NavbarMenu.jsx index dcebd9d..92d71f8 100644 --- a/frontend-vite/src/components/NavbarMenu.jsx +++ b/frontend-vite/src/components/NavbarMenu.jsx @@ -1,33 +1,43 @@ // NavbarMenu.jsx import React, { useEffect, useState } from "react"; +import { useParams, useNavigate } from "react-router-dom"; + +export default function MenuItem({ menuOpen, onClose, assignmentName, assignmentSemester }) { + const navigate = useNavigate(); + const { teamId, assignmentId } = useParams(); -export default function MenuItem({ menuOpen, onClose }) { // Close menu on ESC useEffect(() => { const onKey = (e) => e.key === "Escape" && onClose(); if (menuOpen) window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [menuOpen, onClose]); + console.log(assignmentName); return (<>
- Menu -
- Press - - ESC - - to close +
+
Semester {assignmentSemester}
+
+ {assignmentName} +
+
navigate(`/team/${teamId}/assignments/${assignmentId}/assignmentmarkers`)} + > + Markers +
+ +
+ Press ESC to close +
diff --git a/frontend-vite/src/components/Sidebar.jsx b/frontend-vite/src/components/Sidebar.jsx index 1ac4e9c..c1942f9 100644 --- a/frontend-vite/src/components/Sidebar.jsx +++ b/frontend-vite/src/components/Sidebar.jsx @@ -1,20 +1,105 @@ import { useNavigate, useParams, useLocation } from "react-router-dom"; import React, { useEffect, useState } from "react"; +import { LogOut } from "lucide-react"; +import api from "../utils/axios"; -const navItems = [ + +const allNavItems = [ { label: "Dashboard", path: "dashboard", icon: "/Sidebar/icon/layout.svg" }, { label: "Assignments", path: "assignments", icon: "/Sidebar/icon/clipboard-signature.svg" }, { label: "Markers", path: "markers", icon: "/Sidebar/icon/users.svg" }, - { label: "Flags", path: "flags", icon: "/Sidebar/icon/flag.svg" }, { label: "Reports / Exports", path: "reports", icon: "/Sidebar/icon/file-output.svg" }, { label: "Settings", path: "settings", icon: "/Sidebar/icon/settings.svg" }, ]; -export default function Sidebar() { - const [activeIndex, setActiveIndex] = useState(0); +export default function Sidebar({ activeTab = 0 }) { + const [activeIndex, setActiveIndex] = useState(activeTab); const navigate = useNavigate(); const { teamId } = useParams(); const [username, setUsername] = useState(""); + const [userRole, setUserRole] = useState(null); + + // Helper function to get cached role + const getCachedRole = (teamId) => { + if (!teamId) return null; + try { + const cached = localStorage.getItem(`userRole_${teamId}`); + if (cached) { + const { role, timestamp } = JSON.parse(cached); + // Cache is valid for 5 minutes + const cacheAge = Date.now() - timestamp; + if (cacheAge < 5 * 60 * 1000) { + return role; + } + } + } catch (error) { + console.error("Error reading cached role:", error); + } + return null; + }; + + // Initialize navItems - will be updated immediately if cache exists + const [navItems, setNavItems] = useState(allNavItems.filter(item => item.path !== "reports")); + + // Helper function to cache role + const cacheRole = (teamId, role) => { + try { + localStorage.setItem(`userRole_${teamId}`, JSON.stringify({ + role, + timestamp: Date.now() + })); + } catch (error) { + console.error("Error caching role:", error); + } + }; + + // Helper function to update nav items based on role + const updateNavItems = (role) => { + if (role === "admin") { + setNavItems(allNavItems); + } else { + setNavItems(allNavItems.filter(item => item.path !== "reports")); + } + }; + + const fetchUserRole = async () => { + try { + const token = localStorage.getItem("token"); + if (!token || !teamId) { + console.log("No token or teamId available"); + return; + } + + // Check cache first for immediate UI update + const cachedRole = getCachedRole(teamId); + if (cachedRole) { + setUserRole(cachedRole); + updateNavItems(cachedRole); + } + + // Fetch from API to get fresh data + try { + const res = await api.get(`/team/${teamId}/role`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + const role = res.data.role; + console.log("Fetched user role:", role); + setUserRole(role); + cacheRole(teamId, role); + updateNavItems(role); + } catch (error) { + console.error("Error fetching user role:", error); + // If fetch fails but we have cached role, keep using it + if (!cachedRole) { + // Only clear nav items if we don't have a cached role + setNavItems(allNavItems.filter(item => item.path !== "reports")); + } + } + } catch (error) { + console.error("Error in fetchUserRole:", error); + } + }; const handleNav = (i, path) => { setActiveIndex(i); @@ -22,17 +107,22 @@ export default function Sidebar() { }; const handleLogout = () => { - // Clear user authentication data from storage localStorage.removeItem("token"); localStorage.removeItem("user"); - - // Notify the app that authentication state has changed + // Clear all cached roles + Object.keys(localStorage).forEach(key => { + if (key.startsWith("userRole_")) { + localStorage.removeItem(key); + } + }); window.dispatchEvent(new Event("authChange")); - - // Navigate to the login page navigate("/login"); }; + const handleClickLogo = () => { + navigate(`/team/${teamId}/dashboard`); + }; + useEffect(() => { const userStr = localStorage.getItem("user"); if (userStr) { @@ -41,39 +131,45 @@ export default function Sidebar() { } }, []); + useEffect(() => { + setActiveIndex(activeTab); + }, [activeTab]); + + useEffect(() => { + if (teamId) { + // Check cache immediately for instant UI update + const cachedRole = getCachedRole(teamId); + if (cachedRole) { + setUserRole(cachedRole); + updateNavItems(cachedRole); + } + // Then fetch fresh data + fetchUserRole(); + } + }, [teamId]); + return ( -
+
{/* Logo / Title */}
-
- {/* Circle from public assets */} - Profile Circle - - {username ? username.charAt(0).toUpperCase() : ""} - +
+ Logo
- - Assignment -
{/* Navigation */} -
); } \ No newline at end of file diff --git a/frontend-vite/src/components/forgetpassword-form.jsx b/frontend-vite/src/components/forgetpassword-form.jsx index 2b29750..12bcaf5 100644 --- a/frontend-vite/src/components/forgetpassword-form.jsx +++ b/frontend-vite/src/components/forgetpassword-form.jsx @@ -117,9 +117,11 @@ export function ForgetPasswordForm({ return (
- - - App Logo + + +
+ Logo +
Reset Password
@@ -144,13 +146,13 @@ return ( required value={email} onChange={e => setEmail(e.target.value)} - className={errors.email ? "border-red-500" : ""} + className="border-0" /> {errors.email &&

{errors.email}

}
+
+ +
+ setPassword(e.target.value)} + disabled={isLoading} + className="border-0" + /> + +
- diff --git a/frontend-vite/src/components/signup-form.jsx b/frontend-vite/src/components/signup-form.jsx index 34b1c2c..e5658fd 100644 --- a/frontend-vite/src/components/signup-form.jsx +++ b/frontend-vite/src/components/signup-form.jsx @@ -108,9 +108,11 @@ export function SignupForm({ className, ...props }) { return (
- - - App Logo + + +
+ Logo +
Sign Up
Create an account to access the assignment marking portal @@ -134,7 +136,7 @@ export function SignupForm({ className, ...props }) { required value={email} onChange={e => setEmail(e.target.value)} - className={errors.email ? "border-red-500" : ""} + className="border-0" /> {errors.email &&

{errors.email}

}
@@ -150,7 +152,7 @@ export function SignupForm({ className, ...props }) { required value={password} onChange={e => setPassword(e.target.value)} - className={errors.password ? "border-red-500" : ""} + className="border-0" />
diff --git a/frontend-vite/src/components/ui/accordion.jsx b/frontend-vite/src/components/ui/accordion.jsx new file mode 100644 index 0000000..7226baf --- /dev/null +++ b/frontend-vite/src/components/ui/accordion.jsx @@ -0,0 +1,62 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Accordion({ + ...props +}) { + return ; +} + +function AccordionItem({ + className, + ...props +}) { + return ( + + ); +} + +function AccordionTrigger({ + className, + children, + ...props +}) { + return ( + + svg]:rotate-180", + className + )} + {...props}> + {children} + + + + ); +} + +function AccordionContent({ + className, + children, + ...props +}) { + return ( + +
{children}
+
+ ); +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/frontend-vite/src/components/ui/button.jsx b/frontend-vite/src/components/ui/button.jsx index 69ad71f..1458563 100644 --- a/frontend-vite/src/components/ui/button.jsx +++ b/frontend-vite/src/components/ui/button.jsx @@ -5,16 +5,16 @@ import { cva } from "class-variance-authority"; import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:outline-none aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:outline-none dark:bg-destructive/60", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 focus-visible:outline-none", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: diff --git a/frontend-vite/src/components/ui/calendar.jsx b/frontend-vite/src/components/ui/calendar.jsx index 49b3ad2..fd85781 100644 --- a/frontend-vite/src/components/ui/calendar.jsx +++ b/frontend-vite/src/components/ui/calendar.jsx @@ -1,5 +1,3 @@ -"use client" - import * as React from "react" import { ChevronDownIcon, @@ -85,14 +83,14 @@ function Calendar({ defaultClassNames.week_number ), day: cn( - "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", + "relative w-full h-full text-sm p-0 text-center flex items-center justify-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", defaultClassNames.day ), range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start), range_middle: cn("rounded-none", defaultClassNames.range_middle), range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), today: cn( - "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + "bg-[#d8f275] text-accent-foreground rounded-md data-[selected=true]:rounded-none", defaultClassNames.today ), outside: cn( @@ -164,7 +162,7 @@ function CalendarDayButton({ data-range-end={modifiers.range_end} data-range-middle={modifiers.range_middle} className={cn( - "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", + "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) items-center justify-center leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className )} diff --git a/frontend-vite/src/components/ui/input.jsx b/frontend-vite/src/components/ui/input.jsx index 6dcc84a..29214bf 100644 --- a/frontend-vite/src/components/ui/input.jsx +++ b/frontend-vite/src/components/ui/input.jsx @@ -12,8 +12,7 @@ function Input({ type={type} data-slot="input" className={cn( - "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", - "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className )} {...props} />) diff --git a/frontend-vite/src/components/ui/popover.jsx b/frontend-vite/src/components/ui/popover.jsx index 79cb396..492c8fc 100644 --- a/frontend-vite/src/components/ui/popover.jsx +++ b/frontend-vite/src/components/ui/popover.jsx @@ -28,7 +28,7 @@ function PopoverContent({ align={align} sideOffset={sideOffset} className={cn( - "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border border-slate-300 p-4 shadow-md outline-hidden", className )} {...props} /> diff --git a/frontend-vite/src/components/ui/progress.jsx b/frontend-vite/src/components/ui/progress.jsx new file mode 100644 index 0000000..7b8d2b9 --- /dev/null +++ b/frontend-vite/src/components/ui/progress.jsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +function Progress({ + className, + value, + ...props +}) { + return ( + + + + ); +} + +export { Progress } diff --git a/frontend-vite/src/components/ui/resizable.jsx b/frontend-vite/src/components/ui/resizable.jsx new file mode 100644 index 0000000..901f382 --- /dev/null +++ b/frontend-vite/src/components/ui/resizable.jsx @@ -0,0 +1,51 @@ +import * as React from "react" +import { GripVerticalIcon } from "lucide-react" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "@/lib/utils" + +function ResizablePanelGroup({ + className, + ...props +}) { + return ( + + ); +} + +function ResizablePanel({ + ...props +}) { + return ; +} + +function ResizableHandle({ + withHandle, + className, + ...props +}) { + return ( + div]:rotate-90", + className + )} + {...props}> + {withHandle && ( +
+ +
+ )} +
+ ); +} + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/frontend-vite/src/index.css b/frontend-vite/src/index.css index 7ce7932..bdb09ed 100644 --- a/frontend-vite/src/index.css +++ b/frontend-vite/src/index.css @@ -17,6 +17,7 @@ /* Base styles */ @layer base { + html { font-size: 80%; } body { font-family: 'Inter', ui-sans-serif, system-ui, sans-serif; } @@ -99,6 +100,10 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --color-deakinTeal: var(--deakinTeal); + --color-lime: var(--lime); + --color-purple: var(--purple); + --text-2xs: 0.625rem; } /* Root variables (colors, radii, etc.) */ @@ -135,6 +140,9 @@ --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); + --deakinTeal: #119C8A; + --lime: #d8f275; + --purple: #531DF7; } /* Dark mode overrides */ @@ -170,4 +178,7 @@ --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.556 0 0); + --deakinTeal: #119C8A; + --lime: #d8f275; + --purple: #531DF7; } diff --git a/frontend-vite/src/pages/AssignmentDetails.jsx b/frontend-vite/src/pages/AssignmentDetails.jsx new file mode 100644 index 0000000..c6e94b3 --- /dev/null +++ b/frontend-vite/src/pages/AssignmentDetails.jsx @@ -0,0 +1,453 @@ +import { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import api from "../utils/axios"; + +// --- Main Page Component --- +import Sidebar from "../components/Sidebar"; +import Navbar from "../components/NavbarAssignment"; +import MenuItem from "../components/NavbarMenu"; +import LoadingSpinner from "../components/LoadingSpinner"; + +// --- Helper Components --- + +const ErrorMessage = ({ message }) => ( +
+
+ Error: + {message} +
+
+); + +// --- Admin Comments Display --- +function AdminCommentsDisplay({ rubric, currentUserRole }) { + // Display only admin comments rubric criteria + const criteriaWithComments = rubric?.filter(criterion => + criterion.adminComments && criterion.adminComments.trim() !== "" + ) || []; + + if (criteriaWithComments.length === 0) { + return null; + } + + return ( +
+

+ Admin Feedback + {currentUserRole === 'tutor' && ( + + From Admin + + )} +

+ +
+ {criteriaWithComments.map((criterion) => ( +
+

+ {criterion.categoryName} +

+

+ {criterion.adminComments} +

+
+ ))} +
+
+ ); +} + +// Admin Report Section --- +function AdminReportSection({ teamId, assignmentId, data, currentUserRole }) { + const navigate = useNavigate(); + + if (currentUserRole !== 'admin') { + return null; + } + + const { assignmentDetails, rubric, markers, controlPapers } = data; + + return ( +
+
+

Reports & Analytics

+ +
+ +
+
+
{controlPapers?.length || 0}
+
Control Papers
+
+
+
{markers?.length || 0}
+
Markers
+
+
+
{rubric?.length || 0}
+
Criteria
+
+
+
+ {(() => { + const completedMarkers = markers?.filter(marker => { + const allPapersMarked = controlPapers?.every(paper => + paper.marks?.some(mark => mark.markerId === marker.id) + ); + return allPapersMarked; + }).length || 0; + return `${completedMarkers}/${markers?.length || 0}`; + })()} +
+
Completed
+
+
+ +

+ Access comprehensive reports including deviation analysis, detailed marks comparison, and performance statistics. +

+
+ ); +} + + +function CompletionPrompt({ status, teamId, assignmentId }) { + const navigate = useNavigate(); + + if (status === 'incomplete') { + return ( +
+

Action Required

+

You have not yet submitted your marks for the control papers. Please complete this whenever possible.

+ +
+ ); + } + + if (status === 'complete') { + return ( +
+

All Done!

+

You have successfully completed marking for the control paper.

+
+ ); + } + + return null; +} + + +function ScoreComparisonTable({ data, currentUserRole }) { + const [selectedPaperId, setSelectedPaperId] = useState('cp-A'); + const [isPaperDropdownOpen, setIsPaperDropdownOpen] = useState(false); + const { assignmentDetails, rubric, markers, controlPapers } = data; + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (isPaperDropdownOpen && !event.target.closest('.paper-dropdown-container')) { + setIsPaperDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isPaperDropdownOpen]); + + // Get admin marker (standard) and others + const adminMarker = markers?.find((m) => m.name?.toLowerCase().includes("admin")) || markers?.[0]; + const adminId = adminMarker?.id; + + + const otherMarkers = markers + .filter(m => m.id !== adminId) + .sort((a, b) => a.name.localeCompare(b.name)); + + const selectedPaperData = controlPapers.find(p => p.id === selectedPaperId); + const scoresMap = new Map(); + if (selectedPaperData) { + selectedPaperData.marks.forEach(markerScores => { + const innerMap = new Map(); + markerScores.scores.forEach(s => innerMap.set(s.rubricCategoryId, s.score)); + scoresMap.set(markerScores.markerId, innerMap); + }); + } + + const getScore = (markerId, criterionId) => { + return scoresMap.get(markerId)?.get(criterionId); + }; + + const adminScores = scoresMap.get(adminId); + const isAdminMarked = adminScores && adminScores.size > 0; + + return ( +
+

Marker Score Comparison

+ +
+ +
+ + + {isPaperDropdownOpen && ( +
+ {controlPapers.map((paper) => { + const isSelected = selectedPaperId === paper.id; + return ( + + ); + })} +
+ )} +
+
+ +
+ + + + + + {adminMarker && ( + + )} + + {otherMarkers.map(marker => { + // tutor view: only show self and admin + if ( + currentUserRole !== "admin" && + marker.id !== data.currentUser?.id + ) { + return null; + } + + return ( + + ); + })} + + + + {rubric.map(category => { + const adminScore = adminScores ? getScore(adminId, category.id) : undefined; + + return ( + + + + {adminMarker && ( + + )} + + {otherMarkers.map(marker => { + // tutor 视图:只显示自己和admin + if ( + currentUserRole !== "admin" && + marker.id !== data.currentUser?.id + ) { + return null; + } + + const markerScore = getScore(marker.id, category.id); + let cellColor = 'bg-white'; + if (isAdminMarked && typeof markerScore === 'number' && typeof adminScore === 'number') { + const difference = Math.abs(markerScore - adminScore); + const deviationPercentage = category.deviationScore; + const deviationThreshold = (deviationPercentage / 100) * category.maxScore; + if (difference < deviationThreshold) cellColor = 'bg-green-100 text-green-900'; + else if (difference === deviationThreshold) cellColor = 'bg-yellow-100 text-yellow-900'; + else cellColor = 'bg-red-100 text-red-900'; + } + + return ( + + ); + })} + + ); + })} + +
Rubric Criterion + {adminMarker.name} (Standard) + + {marker.name} +
{category.categoryName} + {typeof adminScore === 'number' ? adminScore : 'N/A'} + + {typeof markerScore === 'number' ? markerScore : 'N/A'} +
+ {!isAdminMarked && ( +
+ The standard marker has not yet marked this control paper. Score coloring is disabled until the standard is set. +
+ )} +
+
+ ); +} + +// --- THE MAIN PAGE COMPONENT (Updated with new components) --- +export default function AssignmentDetails() { + const { teamId, assignmentId } = useParams(); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [assignmentData, setAssignmentData] = useState(null); + const [userCompletionStatus, setUserCompletionStatus] = useState(null); + const [menuOpen, setMenuOpen] = useState(false); + const [currentUserRole, setCurrentUserRole] = useState(null); + + useEffect(() => { + const fetchDetails = async () => { + try { + setIsLoading(true); + const token = localStorage.getItem("token"); + const response = await api.get(`/team/${teamId}/assignments/${assignmentId}/details`, { + headers: { Authorization: `Bearer ${token}` } + }); + + const data = response.data; + setAssignmentData(data); + setCurrentUserRole(data.currentUser?.role); + + // This logic can stay, as the prompt is useful for all users + const { currentUser, controlPapers } = data; + let markedCount = 0; + controlPapers.forEach(paper => { + const userMark = paper.marks.find(mark => mark.markerId === currentUser.id); + if (userMark && userMark.scores.length > 0) { + markedCount++; + } + }); + setUserCompletionStatus(markedCount === controlPapers.length ? 'complete' : 'incomplete'); + + } catch (err) { + setError(err.response?.data?.message || "Failed to load assignment details."); + } finally { + setIsLoading(false); + } + }; + fetchDetails(); + }, [teamId, assignmentId]); + + if (isLoading) return ; + if (error) return ; + if (!assignmentData) return null; + + const { assignmentDetails } = assignmentData; + + return ( +
+ + +
+ + + {/* setMenuOpen(false)} assignmentName={assignmentDetails.course_name} assignmentSemester={assignmentDetails.semester} /> */} + +
+ +
+
+
+ +
+

{assignmentDetails.course_name}

+

+ {assignmentDetails.course_code} • Semester {assignmentDetails.semester} +

+
+ +
+ +
+ + + + {/* Score Comparison Table - 现在传递 currentUserRole */} + + + {/* Admin Comments - for tutor view only*/} + + + {/* Admin Report Section*/} + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend-vite/src/pages/AssignmentMakers.jsx b/frontend-vite/src/pages/AssignmentMakers.jsx new file mode 100644 index 0000000..9bf708b --- /dev/null +++ b/frontend-vite/src/pages/AssignmentMakers.jsx @@ -0,0 +1,69 @@ +import { useState, useEffect } from "react"; +import api from "../utils/axios"; +import { useParams, useNavigate } from "react-router-dom"; + + +import Sidebar from "../components/Sidebar"; +import Navbar from "../components/Navbar"; +import { set } from "date-fns/set"; +import LoadingSpinner from "../components/LoadingSpinner"; + + +export default function AssignmentMakers() { + const { teamId, assignmentId } = useParams(); + const [assignment, setAssignment] = useState([]); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const navigate = useNavigate(); + + useEffect(() => { + const fetchAssignments = async () => { + try { + setIsLoading(true); + const token = localStorage.getItem("token"); + const response = await api.get(`/team/${teamId}/assignments/${assignmentId}/details`, { + headers: { Authorization: `Bearer ${token}` } + }); + const data = response.data.markers; + setAssignment(data); + console.log(response); + } catch (err) { + console.error("Error fetching assignments:", err); + setError(err.response?.data?.message || "Failed to fetch assignments"); + } finally { + setIsLoading(false); + } + }; + + fetchAssignments(); + }, [teamId, assignmentId]); + + if (isLoading) return ; + if (error) return
Error: {error}
; + if (!assignment) return
No assignment data available.
; + + console.log(assignment); + + return ( +
+ +
+ + Assignment Makers Page - Team ID: {teamId}, Assignment ID: {assignmentId} + {assignment.map((marker) => ( +
+ + {/* 'semester' from the API */} +
+ {marker.name} +
+
+ ))} +
+
+ ); +} diff --git a/frontend-vite/src/pages/Assignments.jsx b/frontend-vite/src/pages/Assignments.jsx index 3b48660..766343f 100644 --- a/frontend-vite/src/pages/Assignments.jsx +++ b/frontend-vite/src/pages/Assignments.jsx @@ -1,144 +1,436 @@ -import { useParams, useNavigate, Link } from "react-router-dom"; -import { useEffect, useState } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { useEffect, useState, useMemo } from "react"; import api from "../utils/axios"; import Sidebar from "../components/Sidebar"; import Navbar from "../components/Navbar"; import MenuItem from "../components/NavbarMenu"; +import AssignmentCard from "../components/AssignmentCard"; +import LoadingSpinner from "../components/LoadingSpinner"; export default function Assignments() { const { teamId } = useParams(); const [assignments, setAssignments] = useState([]); const navigate = useNavigate(); - const [menuOpen, setMenuOpen] = useState(false); // navbar menu state + const [menuOpen, setMenuOpen] = useState(false); const [isLoading, setIsLoading] = useState(true); - const [status, setStatus] = useState(""); const [selectedSemester, setSelectedSemester] = useState(""); + const [selectedStatus, setSelectedStatus] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + const [currentUserRole, setCurrentUserRole] = useState(null); + const [currentUserId, setCurrentUserId] = useState(null); + const [isSemesterDropdownOpen, setIsSemesterDropdownOpen] = useState(false); + const [isStatusDropdownOpen, setIsStatusDropdownOpen] = useState(false); + + + + + // Close dropdowns when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (isSemesterDropdownOpen && !event.target.closest('.semester-dropdown-container')) { + setIsSemesterDropdownOpen(false); + } + if (isStatusDropdownOpen && !event.target.closest('.status-dropdown-container')) { + setIsStatusDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isSemesterDropdownOpen, isStatusDropdownOpen]); + + useEffect(() => { + const fetchUserRole = async () => { // Fetch current user's role in the team + const token = localStorage.getItem("token"); + if (!token) return navigate("/login"); + + try { + const res = await api.get(`/team/${teamId}/role`, { + headers: { Authorization: `Bearer ${token}` }, + }); + setCurrentUserRole(res.data.role); + setCurrentUserId(res.data.userId); + } catch (err) { + console.error("Error fetching user role:", err); + } + }; + + fetchUserRole(); + + }, [teamId, navigate]); useEffect(() => { const fetchAssignments = async () => { const token = localStorage.getItem("token"); if (!token) return navigate("/login"); + + setIsLoading(true); try { - const response = await api.get(`/team/${teamId}/assignments`, { + const listRes = await api.get(`/team/${teamId}/assignments`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + const base = (listRes.data.assignments || []).map(a => ({ + ...a, + markers: [], + markersAlreadyMarked: 0, + myCompleted: false, + })); + + const settled = await Promise.allSettled( + base.map(a => + api.get(`/team/${teamId}/assignments/${a.id}/details`, { headers: { Authorization: `Bearer ${token}` }, - }); - setAssignments(response.data.assignments || []); + + }) + ) + ); + + const withMarkers = base.map((a, idx) => { + const r = settled[idx]; + if (r.status === "fulfilled") { + const data = r.value?.data || {}; + const ms = data.markers || []; + + return { + ...a, + // Populate markers and myCompleted based on fetched data + markers: ms.map(m => ({ id: m.id, name: m.name, completed: m.completed ?? false })), + markersAlreadyMarked: data.markersAlreadyMarked ?? 0, + // Use personalComplete from backend to set myCompleted + myCompleted: data.currentUser?.personalComplete ?? false, + }; + } return { ...a, markers: [], markersAlreadyMarked: 0, myCompleted: false }; + }); + + + + + + setAssignments(withMarkers); } catch (err) { console.error("Error fetching assignments:", err); - navigate("/"); } finally { setIsLoading(false); } - }; + fetchAssignments(); - }, [teamId, navigate]); + }, [teamId, navigate, currentUserId]); - if (isLoading) { - return
Loading...
; - } + const markComplete = async (assignmentId, paperId, scores) => { + const token = localStorage.getItem("token"); + if (!token) return navigate("/login"); - return ( -
- {/* Sidebar fixed to the left */} - - - {/* Main content area */} -
- {/* Navbar sits on top */} - setMenuOpen(v => !v)}/> - -
-
setMenuOpen(false)} /> - setMenuOpen(false)} - /> -
-
+ try { + const res = await api.post( + `/assignments/${assignmentId}/mark`, + { paperId, scores }, + { headers: { Authorization: `Bearer ${token}` } } + ); + // Update local state to reflect the marking completion + setAssignments(prev => + prev.map(a => { + if (a.id !== assignmentId) return a; + const updatedMarkers = a.markers.map(m => + m.id === currentUserId ? { ...m, completed: true } : m + ); + const updatedCompletedCount = Math.min( + (a.markersAlreadyMarked || 0) + 1, + a.markers.length + ); + + return { + ...a, + myCompleted: true, + markers: updatedMarkers, + markersAlreadyMarked: updatedCompletedCount, + status: res.data.status || a.status, + }; + }) + ); + } catch (err) { + console.error("Error submitting marks:", err); + } + }; + + // Debug: Log assignments to verify data + assignments.forEach(a => { + console.log(`Assignment: ${a.course_name}`); + console.log("Completed by current user:", a.myCompleted); + console.log("Markers:", a.markers?.map(m => m.name).join(", ") || "No markers"); + console.log("Markers Already Marked:", a.markersAlreadyMarked); + console.log("Status:", a.status); + console.log("assignment", a); + }); + + + // --- Filtering logic --- + const filteredAssignments = useMemo(() => { + return assignments.filter((assignment) => { + const semesterMatch = + !selectedSemester || + selectedSemester === "All Semesters" || + `Semester ${assignment.semester}` === selectedSemester; + + let statusMatch = true; + if (selectedStatus && selectedStatus !== "All Status") { + if (currentUserRole === "admin") { + // Admin sees backend assignment.status + statusMatch = assignment.status?.toUpperCase() === selectedStatus.toUpperCase(); + } else if (currentUserRole === "tutor") { + // Tutor sees own marking progress + const myStatus = assignment.myCompleted ? "COMPLETED" : "MARKING"; + statusMatch = myStatus === selectedStatus.toUpperCase(); + } + } + + let searchMatch = false; + if (currentUserRole === "tutor") { + const myStatus = assignment.myCompleted ? "Completed" : "Marking"; + searchMatch = + assignment.course_name.toLowerCase().includes(searchQuery.toLowerCase()) || + assignment.course_code.toLowerCase().includes(searchQuery.toLowerCase()) || + myStatus.toLowerCase().includes(searchQuery.toLowerCase()); + } else if (currentUserRole === "admin") { + searchMatch = + assignment.course_name.toLowerCase().includes(searchQuery.toLowerCase()) || + assignment.course_code.toLowerCase().includes(searchQuery.toLowerCase()) || + assignment.status.toLowerCase().includes(searchQuery.toLowerCase()); + } + + return semesterMatch && statusMatch && searchMatch; + }); + }, [assignments, selectedSemester, selectedStatus, searchQuery, currentUserRole]); -
- {/* Left: Page Title */} -
- Assignments -
-
- {/* Select and Search form */} -
-
- - {selectedSemester || "Select semester"} - - Dropdown arrow - -
-
- - {selectedSemester || "Select semester"} - - Dropdown arrow - + + + New Assignment + + + )}
-
+ {/* Filters and Search */} +
+ {/* Semester Filter */} +
+ + + {isSemesterDropdownOpen && ( +
+ {[ + { value: "", label: "Select semester", disabled: true }, + { value: "All Semesters", label: "All Semesters" }, + { value: "Semester 1", label: "Semester 1" }, + { value: "Semester 2", label: "Semester 2" } + ].map((option) => { + const isSelected = selectedSemester === option.value; + return ( + + ); + })} +
+ )} +
-
- {assignments.map((assignment) => ( - + + {isStatusDropdownOpen && ( +
+ {[ + { value: "", label: "Select status", disabled: true }, + { value: "All Status", label: "All Status" }, + { value: "MARKING", label: "MARKING" }, + { value: "COMPLETED", label: "COMPLETED" } + ].map((option) => { + const isSelected = selectedStatus === option.value; + return ( + + ); + })} +
+ )} +
+ + {/* Search Bar */} +
+
+ Search Icon + setSearchQuery(e.target.value)} + /> +
+
-
- Due in {assignment.dueInDays} days + + {/* Assignments Grid */} +
+
+ {filteredAssignments.map((assignment) => ( + + ))} + {filteredAssignments.length === 0 && ( +
+ No assignments found +
+ )} +
- - ))}
-
- )}; \ No newline at end of file + ) + +}; diff --git a/frontend-vite/src/pages/CreateAssignment.jsx b/frontend-vite/src/pages/CreateAssignment.jsx index 6dc48bb..5c7b3c4 100644 --- a/frontend-vite/src/pages/CreateAssignment.jsx +++ b/frontend-vite/src/pages/CreateAssignment.jsx @@ -1,12 +1,17 @@ import React, { useState, useEffect } from "react"; -import { useParams } from "react-router-dom"; +import { useParams, useNavigate } from "react-router-dom"; import Sidebar from "../components/Sidebar"; import Navbar from "../components/Navbar"; +import MenuItem from "../components/NavbarMenu"; import api from "../utils/axios"; +import * as XLSX from "xlsx"; -// SHADCN & DATE-FNS IMPORTS + + + +// SHADCN & DATE-DNS IMPORTS import { format } from "date-fns"; -import { Calendar as CalendarIcon } from "lucide-react"; +import { Calendar as CalendarIcon, UploadCloud, File, X } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; @@ -16,9 +21,8 @@ import { PopoverTrigger, } from "@/components/ui/popover"; -// ------------------------------------------------- + // Helper component for auto-expanding textareas -// ------------------------------------------------- const AutoTextarea = ({ value, onChange, placeholder, onContextMenu }) => { const textareaRef = React.useRef(null); @@ -36,26 +40,145 @@ const AutoTextarea = ({ value, onChange, placeholder, onContextMenu }) => { onChange={onChange} onContextMenu={onContextMenu} placeholder={placeholder} - className="w-full h-full bg-transparent resize-none focus:outline-none text-black text-xs font-normal font-['Inter'] leading-normal p-3" - rows={1} + className="w-full h-full bg-transparent resize-none focus:outline-none text-sm font-normal text-slate-900 p-3 placeholder:text-zinc-600" + rows={4} /> ); }; -// ------------------------------------------------- -// Main Component -// ------------------------------------------------- +// Helper function to round to the nearest 0.5 +const roundToHalf = (num) => Math.round(num * 2) / 2; + +// Generates tiers based on standard academic percentages of a total +const generateTiersWithPercentages = (points) => { + const percentages = { hd: 0.85, d: 0.75, c: 0.65, p: 0.50 }; + return [ + { id: crypto.randomUUID(), name: 'High Distinction', description: "", lowerBound: roundToHalf(points * percentages.hd), upperBound: points }, + { id: crypto.randomUUID(), name: 'Distinction', description: "", lowerBound: roundToHalf(points * percentages.d), upperBound: roundToHalf(points * percentages.hd) - 0.5 }, + { id: crypto.randomUUID(), name: 'Credit', description: "", lowerBound: roundToHalf(points * percentages.c), upperBound: roundToHalf(points * percentages.d) - 0.5 }, + { id: crypto.randomUUID(), name: 'Pass', description: "", lowerBound: roundToHalf(points * percentages.p), upperBound: roundToHalf(points * percentages.c) - 0.5 }, + { id: crypto.randomUUID(), name: 'Fail', description: "", lowerBound: 0, upperBound: roundToHalf(points * percentages.p) - 0.5 }, + ]; +}; + + export default function CreateAssignment() { const { teamId } = useParams(); + const navigate = useNavigate(); const [step, setStep] = useState(1); + const [menuOpen, setMenuOpen] = useState(false); + const [uploadedRubricFile, setUploadedRubricFile] = useState(null); // --- STEP 1: ASSIGNMENT DETAILS STATE --- + const [isSemesterDropdownOpen, setIsSemesterDropdownOpen] = useState(false); const [assignmentDetails, setAssignmentDetails] = useState({ courseCode: "", courseName: "", semester: "", dueDate: undefined, }); + + const handleRubricUpload = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + setUploadedRubricFile(file); + + try { + const data = await file.arrayBuffer(); + const workbook = XLSX.read(data); + + // Obtain the first worksheet + const worksheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[worksheetName]; + + // For consistency, convert the sheet to JSON + const jsonData = XLSX.utils.sheet_to_json(worksheet, { defval: "" }); + + if (jsonData.length === 0) { + alert("Excel file is empty. Please upload a valid rubric file."); + return; + } + + + const parsedRubric = jsonData.map((row, index) => { + // try multiple possible column names for each field + const criteria = row.Criteria || row.Criterion || row["Criteria Description"] || ""; + const points = parseFloat(row.Points || row["Total Points"] || row.Score || 20); + const deviation = row.Deviation ? parseFloat(row.Deviation) : ""; + + // Try multiple possible column names for each tier description + const hdDesc = row.HD || row["High Distinction"] || row["HD Description"] || ""; + const dDesc = row.D || row.Distinction || row["D Description"] || ""; + const cDesc = row.C || row.Credit || row["C Description"] || ""; + const pDesc = row.P || row.Pass || row["P Description"] || ""; + const fDesc = row.F || row.Fail || row["F Description"] || ""; + + const totalPoints = isNaN(points) || points <= 0 ? 20 : points; + + return { + id: crypto.randomUUID(), + criteria: criteria || `Criterion ${index + 1}`, + points: totalPoints, + deviation: isNaN(deviation) ? "" : deviation, + tiers: [ + { + id: crypto.randomUUID(), + name: "High Distinction", + description: hdDesc, + lowerBound: roundToHalf(totalPoints * 0.85), + upperBound: totalPoints + }, + { + id: crypto.randomUUID(), + name: "Distinction", + description: dDesc, + lowerBound: roundToHalf(totalPoints * 0.75), + upperBound: roundToHalf(totalPoints * 0.85) - 0.5 + }, + { + id: crypto.randomUUID(), + name: "Credit", + description: cDesc, + lowerBound: roundToHalf(totalPoints * 0.65), + upperBound: roundToHalf(totalPoints * 0.75) - 0.5 + }, + { + id: crypto.randomUUID(), + name: "Pass", + description: pDesc, + lowerBound: roundToHalf(totalPoints * 0.50), + upperBound: roundToHalf(totalPoints * 0.65) - 0.5 + }, + { + id: crypto.randomUUID(), + name: "Fail", + description: fDesc, + lowerBound: 0, + upperBound: roundToHalf(totalPoints * 0.50) - 0.5 + }, + ], + }; + }); + + // Ignore empty criteria or tiers + const validRubric = parsedRubric.filter(item => + item.criteria.trim() !== "" || + item.tiers.some(tier => tier.description.trim() !== "") + ); + + if (validRubric.length === 0) { + alert("Couldn't find valid rubric data in the uploaded file. Please check the file format."); + return; + } + + setRubric(validRubric); + + } catch (err) { + console.error("Error occurs when reading data:", err); + alert(`Fail to read: ${err.message}. Please ensure the file is a valid Excel format.`); + } + }; const handleDetailsChange = (field, value) => { setAssignmentDetails((prev) => ({ ...prev, [field]: value })); @@ -64,158 +187,152 @@ export default function CreateAssignment() { // --- MARKER STATE --- const [markers, setMarkers] = useState([]); const [showMarkerList, setShowMarkerList] = useState(false); - - // --- TEAM MEMBERS STATE --- - const [teamMembers, setTeamMembers] = useState([]); + const [currentUser, setCurrentUser] = useState(null); const [loadingMembers, setLoadingMembers] = useState(true); - // FETCH TEAM MEMBERS + + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (isSemesterDropdownOpen && !event.target.closest('.semester-dropdown-container')) { + setIsSemesterDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isSemesterDropdownOpen]); + + // FETCH CURRENT USER AND TEAM MEMBERS useEffect(() => { - const fetchMembers = async () => { + const fetchUserData = async () => { const token = localStorage.getItem("token"); - if (!token) return; + const userStr = localStorage.getItem("user"); + + if (!token || !userStr) { + setLoadingMembers(false); + navigate("/login"); + return; + } try { - const res = await api.get(`/team/${teamId}/members`, { + const localUser = JSON.parse(userStr); + const userRes = await api.get(`/users/${localUser.id}`, { headers: { Authorization: `Bearer ${token}` }, }); - setTeamMembers(res.data.members || []); + setCurrentUser(userRes.data); + + const teamRes = await api.get(`/team/${teamId}/members`, { + headers: { Authorization: `Bearer ${token}` }, + }); + setTeamMembers(teamRes.data.members || []); } catch (err) { - console.error("Failed to load team members:", err); + console.error("Failed to load user or team members:", err); + if (err.response && (err.response.status === 401 || err.response.status === 403)) { + localStorage.removeItem("token"); + localStorage.removeItem("user"); + navigate("/login"); + } } finally { setLoadingMembers(false); } }; + fetchUserData(); + }, [teamId, navigate]); - fetchMembers(); - }, [teamId]); - - // MARKER HELPER FUNCTIONS - const addMarker = (member) => { - if (!markers.find((m) => m.id === member.id)) { - setMarkers([...markers, member]); + useEffect(() => { + if (currentUser) { + setMarkers(prevMarkers => { + if (!prevMarkers.some(m => m.id === currentUser.id)) { + return [...prevMarkers, currentUser]; + } + return prevMarkers; + }); } - }; + }, [currentUser]); + const [teamMembers, setTeamMembers] = useState([]); + const addMarker = (member) => { if (!markers.find((m) => m.id === member.id)) { setMarkers([...markers, member]); } }; const removeMarker = (id) => { - setMarkers(markers.filter((m) => m.id !== id)); + if (currentUser && String(id) === String(currentUser.id)) { + alert("The assignment creator cannot be removed as a marker."); + return; + } + setMarkers(markers.filter((m) => String(m.id) !== String(id))); }; - - const availableMembers = teamMembers.filter( - (member) => !markers.find((m) => m.id === member.id) + const availableMembers = teamMembers.filter((member) => + !markers.find((m) => String(m.id) === String(member.id)) && + (currentUser ? String(member.id) !== String(currentUser.id) : true) ); - // --- RUBRIC STATE & FUNCTIONS (Step 2) --- - const [rubric, setRubric] = useState([ - { - id: crypto.randomUUID(), - criteria: "", - tiers: [ - { id: crypto.randomUUID(), value: "" }, - { id: crypto.randomUUID(), value: "" }, - ], - points: 20, - deviation: 2, - }, - ]); - + // --- STEP 2: RUBRIC STATE & FUNCTIONS --- + const [rubric, setRubric] = useState([{ id: crypto.randomUUID(), criteria: "", tiers: generateTiersWithPercentages(20), points: 20, deviation: "", },]); const [contextMenu, setContextMenu] = useState(null); + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (isSemesterDropdownOpen && !event.target.closest('.semester-dropdown-container')) { + setIsSemesterDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isSemesterDropdownOpen]); + useEffect(() => { const handleClickOutside = () => setContextMenu(null); window.addEventListener("click", handleClickOutside); return () => window.removeEventListener("click", handleClickOutside); }, []); - const updateCriterionText = (criterionId, value) => - setRubric( - rubric.map((c) => - c.id === criterionId ? { ...c, criteria: value } : c - ) - ); - - const updateTierText = (criterionId, tierIndex, value) => - setRubric( - rubric.map((c) => - c.id === criterionId - ? { - ...c, - tiers: c.tiers.map((t, i) => - i === tierIndex ? { ...t, value } : t - ), - } - : c - ) - ); - - const updatePoints = (criterionId, value) => - setRubric( - rubric.map((c) => - c.id === criterionId ? { ...c, points: Number(value) } : c - ) - ); - - const updateDeviation = (criterionId, value) => - setRubric( - rubric.map((c) => - c.id === criterionId ? { ...c, deviation: Number(value) } : c - ) - ); - - const addCriterion = () => { - setRubric([ - ...rubric, - { - id: crypto.randomUUID(), - criteria: "", - tiers: [{ id: crypto.randomUUID(), value: "" }], - points: 0, - deviation: 0, - }, - ]); - }; - - const addRatingTier = (criterionId) => { - setRubric( - rubric.map((c) => - c.id === criterionId - ? { ...c, tiers: [...c.tiers, { id: crypto.randomUUID(), value: "" }] } - : c - ) - ); + const updateCriterionText = (criterionId, value) => setRubric(rubric.map((c) => (c.id === criterionId ? { ...c, criteria: value } : c))); + const updateTierDescription = (criterionId, tierIndex, value) => { + setRubric(rubric.map((c) => { + if (c.id === criterionId) { + const newTiers = c.tiers.map((t, i) => (i === tierIndex ? { ...t, description: value } : t)); + return { ...c, tiers: newTiers }; + } + return c; + })); }; - - const deleteTier = (criterionId, tierIndex) => { - setRubric( - rubric.map((c) => - c.id === criterionId - ? { ...c, tiers: c.tiers.filter((_, i) => i !== tierIndex) } - : c - ) - ); + const updatePoints = (criterionId, value) => { + const newPoints = parseFloat(value); + if (isNaN(newPoints) || newPoints < 0) return; + setRubric(rubric.map((c) => c.id === criterionId ? { ...c, points: newPoints, tiers: generateTiersWithPercentages(newPoints) } : c)); }; - - const deleteColumn = (tierIndex) => - setRubric( - rubric.map((c) => ({ - ...c, - tiers: c.tiers.filter((_, i) => i !== tierIndex), - })) - ); - - const deleteRow = (criterionId) => - setRubric(rubric.filter((c) => c.id !== criterionId)); - - const handleRightClick = (e, type, criterionId, tierIndex = null) => { - e.preventDefault(); - e.stopPropagation(); - setContextMenu({ x: e.pageX, y: e.pageY, type, criterionId, tierIndex }); + const updateDeviation = (criterionId, value) => { + const percentage = parseFloat(value); + if (isNaN(percentage) || percentage < 0 || percentage > 100) return; + setRubric(rubric.map((criterion) => criterion.id === criterionId ? { ...criterion, deviation: percentage } : criterion)); + } + const addCriterion = () => setRubric([...rubric, { id: crypto.randomUUID(), criteria: "", tiers: generateTiersWithPercentages(20), points: 20, deviation: "", },]); + const updateTierLowerBound = (criterionId, tierIndex, newLowerBoundStr) => { + const value = parseFloat(newLowerBoundStr); + setRubric(currentRubric => { + const newRubric = JSON.parse(JSON.stringify(currentRubric)); + const criterion = newRubric.find(c => c.id === criterionId); + if (!criterion) return currentRubric; + const tiers = criterion.tiers; + const totalPoints = criterion.points; + if (isNaN(value) || value < 0 || value > totalPoints || (tierIndex > 0 && value >= tiers[tierIndex - 1].lowerBound) || (tierIndex < tiers.length - 2 && value <= tiers[tierIndex + 1].lowerBound)) return currentRubric; + tiers[tierIndex].lowerBound = value; + if (tierIndex < tiers.length - 1) { + tiers[tierIndex + 1].upperBound = value - 0.5; + } + return newRubric; + }); }; - - const handleContextMenuAction = (action) => { + const deleteRow = (criterionId) => { + /*if (rubric.length < 2) { + window.alert("At least one row of rubric is required."); + return; + }*/ + setRubric(rubric.filter((c) => c.id !== criterionId))}; + const handleRightClick = (e, criterionId) => { e.preventDefault(); setContextMenu({ x: e.pageX, y: e.pageY, criterionId }); }; + const handleContextMenuAction = (action) => { if (!contextMenu) return; - const { type, criterionId, tierIndex } = contextMenu; - + const { criterionId } = contextMenu; if (action === "delete-row") { if (rubric.length <= 1) { alert("You cannot delete the last criterion."); @@ -223,467 +340,712 @@ export default function CreateAssignment() { deleteRow(criterionId); } } + setContextMenu(null); + }; - if (action === "delete-box" && type === "rating") { - const criterion = rubric.find((c) => c.id === criterionId); - if (criterion && criterion.tiers.length <= 1) { - alert("Each criterion must have at least one rating box."); - } else { - deleteTier(criterionId, tierIndex); + // --- STEP 3: CONTROL PAPERS STATE --- + const [controlPaper, setControlPaper] = useState(null); + + + // --- Navigation and Submission Logic --- + const handleNextStep = () => { + if (step === 1) { + if (!assignmentDetails.courseCode || !assignmentDetails.courseName || !assignmentDetails.semester || !assignmentDetails.dueDate) { + alert("Please fill out all assignment details."); + return; + } + setStep(2); + } else if (step === 2) { + for (const criterion of rubric) { + if (criterion.criteria.trim() === "") { + alert("Please fill out all 'Criteria' descriptions in the rubric."); + return; + } + for (const tier of criterion.tiers) { + if (tier.description.trim() === "") { + alert(`Please fill out the description for '${tier.name}' in the rubric.`); + return; + } + } + } + setStep(3); + } else if (step === 3) { + if (!controlPaper) { + alert("Please upload Control Paper before proceeding."); + return; } + setStep(4); } + }; - if (action === "delete-column" && type === "rating") { - const canDelete = rubric.every( - (c) => c.tiers.length > 1 || c.tiers.length <= tierIndex - ); - if (!canDelete) { - alert( - "You cannot delete this column as it would leave a criterion with no rating boxes." - ); + const handleCreate = async () => { + if (!controlPaper) { + alert("Please upload the Control Paper before creating the assignment."); + return; + } + + const formData = new FormData(); + formData.append('controlPaper', controlPaper); + console.log('Uploading control paper:'); + + const payload = { + assignmentDetails: { ...assignmentDetails, teamId: teamId }, + markers: markers.map(marker => marker.id), + rubric: rubric, + }; + formData.append('assignmentData', JSON.stringify(payload)); + + + try { + const response = await api.post('/assignments', formData, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + + if (response.status === 201) { + alert("Assignment created successfully!"); + navigate(`/team/${teamId}/dashboard`); } else { - deleteColumn(tierIndex); + alert(`An issue occurred: ${response.data.message || 'Please try again.'}`); } + } catch (error) { + console.error("Failed to create assignment:", error); + const errorMessage = error.response?.data?.message || "An error occurred while creating the assignment."; + alert(`Error: ${errorMessage}`); } - - setContextMenu(null); }; - // This calculation is the "brain" behind the smart alignment. - const maxTiers = Math.max(1, ...rubric.map((c) => c.tiers.length)); + - // ------------------------------------------------- - // RENDER - // ------------------------------------------------- return (
- -
- -
- {step === 1 && ( - <> - {/* Header */} -
- - Create New Assignment/
-
- - Assignment Info
-
-
+ +
+ setMenuOpen(v => !v)} /> + setMenuOpen(false)} /> +
+
+ {step === 1 && ( + <> + {/* Header */} +
+ + Create New Assignment/
+
+ + Assignment Info
+
+
- {/* Assignment Info Inputs */} -
-
-
- {/* Course Code */} -
-
- Course Code + {/* Assignment Info Inputs */} +
+
+
+ {/* Course Code */} +
+
+ Course Code +
+ + handleDetailsChange("courseCode", e.target.value) + } + /> +
+ + {/* Course Name */} +
+
+ Course Name +
+ + handleDetailsChange("courseName", e.target.value) + } + />
- - handleDetailsChange("courseCode", e.target.value) - } - />
- {/* Course Name */} + {/* Due Date */}
-
- Course Name +
+ Due Date
- - handleDetailsChange("courseName", e.target.value) - } - /> + + + + + + handleDetailsChange("dueDate", date)} + initialFocus + /> + +
-
- {/* Due Date */} -
-
- Due Date -
- - - - - - handleDetailsChange("dueDate", date)} - initialFocus - /> - - -
- - {/* Semester */} -
-
- Semester -
-
- - {assignmentDetails.semester - ? `Semester ${assignmentDetails.semester}` - : "Select semester"} - - Dropdown arrow - + + {assignmentDetails.semester + ? `Semester ${assignmentDetails.semester}` + : "Select semester"} + + Dropdown arrow + + + {isSemesterDropdownOpen && ( +
+ {[ + { value: "", label: "Select semester", disabled: true }, + { value: "1", label: "Semester 1" }, + { value: "2", label: "Semester 2" } + ].map((option) => { + const isSelected = assignmentDetails.semester === option.value; + return ( + + ); + })} +
+ )} +
-
- {/* Markers */} -
-
- Markers -
+ {/* Markers */} +
+
+ Markers +
- {/* Marker Cards */} -
- {markers.map((marker) => ( -
-
-
- {marker.id} -
-
removeMarker(marker.id)} - > - Remove + {/* Marker Cards */} +
+ {markers.map((marker) => ( +
+
+
+ {marker.id} +
+
removeMarker(marker.id)} + > + {currentUser && String(marker.id) === String(currentUser.id) ? 'Creator (Cannot Remove)' : 'Remove'} +
-
-
-
-
- {marker.username} +
+
+
+ {marker.username} +
-
- ))} + ))} - {/* Add Marker Button */} -
setShowMarkerList(!showMarkerList)} - > -
+
+ {/* Add Marker Button */} +
setShowMarkerList(!showMarkerList)} + > +
+
+
-
- {/* Marker List Dropdown */} - {showMarkerList && ( -
- {loadingMembers ? ( -
Loading team members...
- ) : availableMembers.length === 0 ? ( -
- No team members available. -
- ) : ( - availableMembers.map((member) => ( -
-
-
-
- {member.username} + {/* Marker List Dropdown */} + {showMarkerList && ( +
+ {loadingMembers ? ( +
Loading team members...
+ ) : availableMembers.length === 0 ? ( +
+ No other team members available to add. +
+ ) : ( + availableMembers.map((member) => ( +
+
+
+
+ {member.username} +
+
- -
- )) - )} + )) + )} -
- +
+ +
-
- )} -
- - )} - - - {/* ---------------- Step 2: Rubric Setup ---------------- */} - {step === 2 && ( -
- - Create New Assignment/
-
- - Rubric Setup
-
- -
- Assignment Criteria + )} +
+ + )} + + {step === 2 && ( +
+ + Create New Assignment/
+
+ + Rubric Setup
+
+ + {/* Upload Rubric XLSX */} +
+ + + {uploadedRubricFile ? ( +
+
+ + + {uploadedRubricFile.name} + +
+ +
+ ) : ( + + )} +
+ + {/* Rubric Table Header */} +
+ Assignment Criteria +
+ +
+ {/* Header Row */} +
+
+
+
+
+
+ Criteria Description +
+
+
+
High Distinction
+
+
+
Distinction
+
+
+
Credit
+
+
+
Pass
+
+
+
Fail
+
+
+
Pts
+
+
+
Deviation
+
+
+ + {/* Dynamic Rubric Rows */} + {rubric.map((criterion) => ( +
+
+ +
+ +
handleRightClick(e, criterion.id)} + > + + updateCriterionText(criterion.id, e.target.value) + } + placeholder="Criterion..." + /> +
+ + + {criterion.tiers.map((tier, tierIndex) => ( +
handleRightClick(e, criterion.id)} + > + + updateTierDescription(criterion.id, tierIndex, e.target.value) + } + placeholder={`Describe ${tier.name}...`} + /> +
+ + updateTierLowerBound(criterion.id, tierIndex, e.target.value) + } + className="w-12 text-center text-xs text-slate-900 bg-transparent focus:outline-none" + disabled={tierIndex === criterion.tiers.length - 1} + /> + - + + {tier.upperBound} +
+
+ ))} - {/* Rubric Table */} -
- {/* Header Row */} -
-
- Criteria -
-
- Ratings -
-
- Points -
-
- Deviation -
-
+
handleRightClick(e, criterion.id)} + > + updatePoints(criterion.id, e.target.value)} + className="w-full text-center bg-white rounded-md outline outline-1 outline-offset-[-1px] outline-[#E4E4E7] text-xs text-slate-900 p-2 placeholder:text-zinc-600" + placeholder="Pts" + /> +
- {/* Data Rows */} - {rubric.map((criterion) => ( -
- handleRightClick(e, "criterion", criterion.id) - } - > - {/* Criterion Column */} -
- - updateCriterionText(criterion.id, e.target.value) - } - placeholder="Describe criterion..." - /> -
+
handleRightClick(e, criterion.id)} + > +
+ ± + + updateDeviation(criterion.id, e.target.value) + } + className="w-full text-center text-xs text-slate-900 placeholder:text-zinc-600" + placeholder="Dev" + /> +
+
+
+ ))} +
- {/* Ratings Column */} -
- {criterion.tiers.map((tier, tierIndex) => ( -
- handleRightClick(e, "rating", criterion.id, tierIndex) - } - > - - updateTierText(criterion.id, tierIndex, e.target.value) - } - placeholder="Describe rating..." - /> -
- ))} +
+ + +
- {/* Extra Add Rating Button */} - {criterion.tiers.length < maxTiers && ( -
- -
- )} -
+ {contextMenu && ( +
+
handleContextMenuAction("delete-row")} + > + Delete this Criterion Row +
+
+ )} +
+)} - {/* Points Column */} -
- - - updatePoints(criterion.id, e.target.value) - } - className="w-full text-center bg-slate-50/50 rounded-md border border-slate-300 text-sm p-1 font-medium" - /> -
+ {step === 3 && ( +
+ {/* Header */} +
+ Create New Assignment/
+ Control Paper Uploads
+
+

Upload the two control papers for this assignment. These will be used to standardise marking across all assigned tutors.

- {/* Deviation Column */} -
-
- ± +
+ {/* Uploader for Control Paper */} +
+ + {controlPaper ? ( +
+
+ + {controlPaper.name} +
+ +
+ ) : ( +
-
+ + )}
- ))} +
+ )} - {/* Add Criterion */} -
- - + Add New Criterion - -
+ {step === 4 && ( +
+ {/* Header */} +
+ Create New Assignment/
+ Review & Create
+
- {/* Context Menu */} - {contextMenu && ( -
- {contextMenu.type === "rating" && ( - <> -
handleContextMenuAction("delete-box")} - > - Delete this Rating Box + {/* Assignment Details Review */} +
Assignment Information
+
+
Course Code: {assignmentDetails.courseCode}
+
Course Name: {assignmentDetails.courseName}
+
Semester: Semester {assignmentDetails.semester}
+
Due Date: {assignmentDetails.dueDate ? format(assignmentDetails.dueDate, "PPP") : 'N/A'}
+
+ + {/* Markers Review */} +
Assigned Markers
+
+ {markers.map((marker) => ( +
+
+
{marker.id}
-
- handleContextMenuAction("delete-column") - } - > - Delete this Rating Column +
+
+
+ {marker.username} {currentUser && String(marker.id) === String(currentUser.id) && '(Creator)'} +
- - )} -
handleContextMenuAction("delete-row")} - > - Delete this Criterion +
+ ))} +
+ + {/* Control Paper Review */} +
+ Control Paper +
+
+ +
+

Control Paper

+

{controlPaper?.name || 'No file selected'}

- )} -
- )} - {/* ---------------- Footer Buttons ---------------- */} -
- + {/* Rubric Review */} +
Final Rubric
+
+ {/* Header Row */} +
+
+
Criteria
+
High Distinction
+
Distinction
+
Credit
+
Pass
+
Fail
+
Pts
+
Deviation
+
- {step === 2 && ( - + {/* Data Rows */} + {rubric.map((criterion) => ( +
+

{criterion.criteria}

+ {criterion.tiers.map((tier) => ( +
+

{tier.description}

+
+ {tier.lowerBound} + - + {tier.upperBound} +
+
+ ))} +

{criterion.points}

+

± {criterion.deviation}

+
+ ))} +
+
)} - {step === 1 && ( - - )} +
+ {step > 1 && ( + + )} + {step < 4 && ( + + )} + {step === 4 && ( + + )} +
); -} +} \ No newline at end of file diff --git a/frontend-vite/src/pages/CreateTeam.jsx b/frontend-vite/src/pages/CreateTeam.jsx index 96702e7..1d030f4 100644 --- a/frontend-vite/src/pages/CreateTeam.jsx +++ b/frontend-vite/src/pages/CreateTeam.jsx @@ -1,5 +1,10 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import api from "../utils/axios"; export default function CreateTeam() { @@ -41,25 +46,54 @@ export default function CreateTeam() { }; return ( -
-

Create a New Team

-
-
- - setName(e.target.value)} - className="w-full p-2 border rounded" - /> -
- +
+
+ + +
+ Logo +
+
+ Create a new team for your assignment marking portal +
+
+ +
{ + e.preventDefault(); + handleCreateTeam(); + }} + > +
+
+ + setName(e.target.value)} + disabled={isLoading} + className="border-0" + /> +
+ + + + +
+
+
+
); diff --git a/frontend-vite/src/pages/Forgetpassword.jsx b/frontend-vite/src/pages/Forgetpassword.jsx index e357231..6dccea8 100644 --- a/frontend-vite/src/pages/Forgetpassword.jsx +++ b/frontend-vite/src/pages/Forgetpassword.jsx @@ -5,10 +5,10 @@ import { ForgetPasswordForm } from "@/components/forgetpassword-form"; export default function changePassword() { return (
diff --git a/frontend-vite/src/pages/IndividualDashboard.jsx b/frontend-vite/src/pages/IndividualDashboard.jsx new file mode 100644 index 0000000..54ed5ca --- /dev/null +++ b/frontend-vite/src/pages/IndividualDashboard.jsx @@ -0,0 +1,202 @@ +import { useParams, useNavigate, Link } from "react-router-dom"; +import { useEffect, useState } from "react"; +import api from "../utils/axios"; +import Sidebar from "../components/Sidebar"; +import Navbar from "../components/Navbar"; +import MenuItem from "../components/NavbarMenu"; +import LoadingSpinner from "../components/LoadingSpinner"; + +export default function IndividualDashboard() { + const { teamId } = useParams(); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(true); + + const [menuOpen, setMenuOpen] = useState(false); // navbar menu state + + // Tabs + const [activeTab, setActiveTab] = useState("members"); + + // Invite modal state + const [showInviteModal, setShowInviteModal] = useState(false); + const [inviteEmail, setInviteEmail] = useState(""); + const [emailsList, setEmailsList] = useState([]); + const [inviteStatus, setInviteStatus] = useState(null); + + useEffect(() => { + const fetchTeamData = async () => { + const token = localStorage.getItem("token"); + if (!token) return navigate("/login"); + + try { + + } catch (err) { + console.error("Failed to fetch team data:", err); + navigate("/"); + } finally { + setIsLoading(false); + } + }; + + fetchTeamData(); + }, [teamId, navigate]); + + if (isLoading) return ; + + return ( +
+ {/* Sidebar fixed to the left */} + + + {/* Main content area */} +
+ {/* Navbar sits on top */} + setMenuOpen(v => !v)}/> + setMenuOpen(false)} + /> +
+ + {/* Page Header Section */} +
+ {/* Left: Page Title */} +
+ Dashboard +
+ + {/* Right: New Assignment Button */} + +
+ + {/* Page Content */} +
+ + {/* Left wrapper: Boxes 1-4 + wide box */} +
+
+ {/* Boxes 1-4 */} +
+
+
+
Total Assignments
+
+
+
+
48
+
+
+
+
+
+
Markers Active
+
+
+
+
12/18
+
+
+
+
+
+
Submissions Graded
+
+
+
+
342/450
+
+
+
+
+
+
Flags Open
+
+
+
+
3
+
+
+
+ + {/* Wide box under the first 4 boxes */} +
+ + {/* Second Wide box under the first 4 boxes */} +
+
+ + {/* Right wrapper: Stacked boxes */} +
{/* The wrapper for all the stacked boxes */} +
{/* The actual border for the first box */} + {/* Quick Actions Content */} +
{/* Padding inside the whole box */} + {/* Title */} +
+ Quick Actions +
+ +
+ {/* Invite Markers */} + + Invite Markers +
+ Invite Markers +
+ + + + {/* Email Markers */} +
+ Email Markers +
+ Email Markers +
+
+ + {/* Upcoming Deadlines */} +
+ Upcoming Deadlines +
+ Upcoming Deadlines +
+
+ + {/* Export Reports */} +
+ Export Reports +
+ Export Reports +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ ); +} diff --git a/frontend-vite/src/pages/InviteMarkers.jsx b/frontend-vite/src/pages/InviteMarkers.jsx index 20983b0..bf66bed 100644 --- a/frontend-vite/src/pages/InviteMarkers.jsx +++ b/frontend-vite/src/pages/InviteMarkers.jsx @@ -10,47 +10,14 @@ export default function InviteMarkers() { const [inviteEmail, setInviteEmail] = useState(""); const [emailsList, setEmailsList] = useState([]); + const [inviteMessage, setInviteMessage] = useState(""); const [inviteStatus, setInviteStatus] = useState(null); const [loading, setLoading] = useState(false); const [teamMembers, setTeamMembers] = useState([]); - // Fetch current team members (so we don’t invite existing users) - useEffect(() => { - async function fetchMembers() { - try { - const token = localStorage.getItem("token"); - const res = await api.get(`/team/${teamId}/members`, { - headers: { Authorization: `Bearer ${token}` }, - }); - setTeamMembers(res.data.members.map((m) => m.username.toLowerCase())); - } catch (err) { - console.error("Error fetching team members:", err); - } - } - fetchMembers(); - }, [teamId]); - - const handleAddEmail = () => { - const email = inviteEmail.trim().toLowerCase(); - if (!email) return; - - if (teamMembers.includes(email)) { - setInviteStatus(`⚠️ ${email} is already a team member.`); - return; - } - - if (!emailsList.includes(email)) { - setEmailsList([...emailsList, email]); - setInviteEmail(""); - setInviteStatus(null); - } - }; + - const handleRemoveEmail = (email) => { - setEmailsList(emailsList.filter((e) => e !== email)); - }; - - const handleInviteMultiple = async () => { + /*const handleInviteMultiple = async () => { const token = localStorage.getItem("token"); if (emailsList.length === 0) { setInviteStatus("Add at least one email."); @@ -61,26 +28,89 @@ export default function InviteMarkers() { try { await api.post( `/team/${teamId}/invite`, - { emails: emailsList }, + { + emails: emailsList, + message: inviteMessage // Send the message along with invites + }, { headers: { Authorization: `Bearer ${token}` } } ); setInviteStatus("✅ Invitations sent successfully!"); setEmailsList([]); + setInviteMessage(""); // Clear message after sending } catch (err) { console.error("Failed to send invites:", err); setInviteStatus("❌ Failed to send invites."); } finally { setLoading(false); } - }; - + };*/ + const handleInviteMultiple = async () => { + const token = localStorage.getItem("token"); + if (emailsList.length === 0) { + setInviteStatus("Add at least one email."); + return; + } + + setLoading(true); + try { + console.log("Sending invite request:", { + emails: emailsList, + message: inviteMessage, + teamId + }); + + const response = await api.post( + `/team/${teamId}/invite`, + { + emails: emailsList, + message: inviteMessage + }, + { headers: { Authorization: `Bearer ${token}` } } + ); + + console.log("Invite response:", response.data); + setInviteStatus("✅ Invitations sent successfully!"); + setEmailsList([]); + setInviteMessage(""); + } catch (err) { + console.error("Failed to send invites:", err); + console.error("Error details:", err.response?.data); + setInviteStatus(`❌ Failed to send invites: ${err.response?.data?.error || err.message}`); + } finally { + setLoading(false); + } +}; + + +const handleAcceptInvite = async () => { + try { + const token = localStorage.getItem('token'); // 用户登录token + const response = await fetch(`/api/team/invite/${inviteToken}/respond`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + action: 'accept' + }) + }); + + const result = await response.json(); + console.log('Accept response:', result); + } catch (error) { + console.error('Error accepting invite:', error); + } +}; return (
{/* Sidebar */} - + {/* Main content */} -
+
@@ -94,6 +124,9 @@ export default function InviteMarkers() { onChange={(e) => setInviteEmail(e.target.value)} placeholder="Enter marker email" className="px-3 py-2 border border-gray-300 rounded-md flex-1" + onKeyPress={(e) => { + if (e.key === 'Enter') handleAddEmail(); + }} />