diff --git a/README.md b/README.md index edaeb8e3..b727a67a 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,16 @@ The central monorepo for club operations and digital infrastructure. ## Workspace layout -| Path | Contents | -| --- | --- | -| `sites/mainweb` | Public club site | -| `sites/hacklytics2027` | Hacklytics 2027 event site (static export) | -| `packages/db` | Drizzle schema, client, seed script | -| `packages/api` | tRPC routers | -| `packages/auth` | NextAuth configuration | -| `packages/ui`, `packages/consts` | Shared components and constants | -| `tooling/*` | Shared eslint / tailwind / tsconfig | +| Path | Contents | +| -------------------------------- | ------------------------------------------ | +| `apps/dsgt-slack` | Custom Slack Bolt bot (`@dsgt`) | +| `sites/mainweb` | Public club site | +| `sites/hacklytics2027` | Hacklytics 2027 event site (static export) | +| `packages/db` | Drizzle schema, client, seed script | +| `packages/api` | tRPC routers | +| `packages/auth` | NextAuth configuration | +| `packages/ui`, `packages/consts` | Shared components and constants | +| `tooling/*` | Shared eslint / tailwind / tsconfig | ## Database diff --git a/apps/dsgt-slack/.env.example b/apps/dsgt-slack/.env.example new file mode 100644 index 00000000..444d2017 --- /dev/null +++ b/apps/dsgt-slack/.env.example @@ -0,0 +1,19 @@ +# Copy to .env and fill in values from https://api.slack.com/apps +# Never commit .env or real tokens. + +# Bot User OAuth Token (xoxb-...) from OAuth & Permissions after install +SLACK_BOT_TOKEN=xoxb-your-bot-token + +# Signing Secret from Basic Information +SLACK_SIGNING_SECRET=your-signing-secret + +# App-level token (xapp-...) from Basic Information → App-Level Tokens +# Required for Socket Mode. Create a token with the connections:write scope. +SLACK_APP_TOKEN=xapp-your-app-token + +# true (default) uses Socket Mode for local development. +# Set to false to listen on HTTP for the Events API (Request URL: /slack/events). +SLACK_SOCKET_MODE=true + +# Used only when SLACK_SOCKET_MODE=false +PORT=3000 diff --git a/apps/dsgt-slack/README.md b/apps/dsgt-slack/README.md new file mode 100644 index 00000000..0519d16a --- /dev/null +++ b/apps/dsgt-slack/README.md @@ -0,0 +1,116 @@ +# dsgt Slack bot + +Bolt for JavaScript app for **Data Science @ Georgia Tech (DS@GT)**. In Slack it +appears as **@dsgt**. This is a custom Slack app owned by the club, not the +Cursor Slack integration. + +The bot answers `@dsgt` mentions, direct messages, and the `/dsgt` slash +command. It can confirm that it is online (`ping`) and tell people how to join: +the first event is **August 26 (8/26)**, further details will be announced +there, and the club site is datasciencegt.org. + +## Create the Slack app from the manifest + +1. Open [https://api.slack.com/apps](https://api.slack.com/apps) and sign in to + the workspace that should host the bot (typically the DS@GT Slack). +2. Click **Create New App** → **From an app manifest**. +3. Select the workspace, then paste the contents of + [`manifest.yaml`](./manifest.yaml) (YAML is accepted). +4. Confirm the summary. The app name and bot user display name should both be + `dsgt`. Socket Mode should be enabled. +5. Click **Create**. + +You now have a real Slack app whose bot user is **dsgt**. + +## Install into a workspace and collect tokens + +1. In the app settings sidebar, open **OAuth & Permissions** and click + **Install to Workspace**. Approve the requested bot scopes. +2. Copy the **Bot User OAuth Token** (`xoxb-...`) into `.env` as + `SLACK_BOT_TOKEN`. +3. Open **Basic Information** and copy the **Signing Secret** into `.env` as + `SLACK_SIGNING_SECRET`. +4. On **Basic Information**, under **App-Level Tokens**, click + **Generate Token and Scopes**. Name it something like `socket-mode`, add the + `connections:write` scope, and generate it. Copy the `xapp-...` value into + `.env` as `SLACK_APP_TOKEN`. + +Socket Mode is already on in the manifest, so you do not need a public URL for +local development. + +## Run locally (Socket Mode) + +From the monorepo root: + +```bash +cp apps/dsgt-slack/.env.example apps/dsgt-slack/.env +# edit apps/dsgt-slack/.env with the three tokens above + +pnpm install +pnpm --filter @query/dsgt-slack dev +``` + +`pnpm dev` at the repo root also starts this app (turbo `dev`), alongside the +sites. + +When the process logs that the bot is running, it is connected to Slack. + +### Invite @dsgt to a channel + +In the channel: + +```text +/invite @dsgt +``` + +Then mention it (`@dsgt ping`) or run `/dsgt help`. Direct messages work from +the app's **Messages** tab without an invite. + +### Quick checks after install + +| Action | Expected reply | +| ----------------------------------------- | ----------------------------------------------- | +| `@dsgt ping` or `/dsgt ping` | Health check confirming the bot is online | +| `/dsgt help` | List of topics | +| “How do I join?” in a DM, or `/dsgt join` | August 26 first-event FAQ and datasciencegt.org | + +## Host later over HTTP (Events API) + +The same process can listen for Slack’s HTTP Events API instead of Socket Mode. +Bolt serves Slack at **`/slack/events`**. + +1. Deploy the app with a public HTTPS URL. +2. Set `SLACK_SOCKET_MODE=false` and `PORT` (default `3000`) in the environment. + `SLACK_APP_TOKEN` is not required in this mode. +3. In the Slack app settings: + - Turn **Socket Mode** off. + - **Event Subscriptions** → Request URL: + `https:///slack/events` + - **Slash Commands** → `/dsgt` → Request URL: + `https:///slack/events` + - **Interactivity & Shortcuts** → Request URL: + `https:///slack/events` +4. Start with `pnpm --filter @query/dsgt-slack start`. +5. `GET https:///health` should return `ok`. + +Reinstall the app if Slack asks you to after changing URLs or scopes. + +## Scripts + +| Script | Command | +| ----------- | ------------------------------------------- | +| Dev (watch) | `pnpm --filter @query/dsgt-slack dev` | +| Start | `pnpm --filter @query/dsgt-slack start` | +| Lint | `pnpm --filter @query/dsgt-slack lint` | +| Typecheck | `pnpm --filter @query/dsgt-slack typecheck` | +| Test | `pnpm --filter @query/dsgt-slack test` | + +Repo-wide `pnpm lint`, `pnpm typecheck`, `pnpm build`, and `pnpm test` include +this package (`pnpm test` runs the join-FAQ unit test along with the existing +vitest targets). + +## Environment + +See [`.env.example`](./.env.example). Do not commit `.env` or real tokens. +`SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, and `SLACK_APP_TOKEN` (Socket Mode) +are required to start the process. diff --git a/apps/dsgt-slack/eslint.config.mjs b/apps/dsgt-slack/eslint.config.mjs new file mode 100644 index 00000000..286e0415 --- /dev/null +++ b/apps/dsgt-slack/eslint.config.mjs @@ -0,0 +1,12 @@ +import { config } from "@query/eslint-config/base"; + +/** @type {import("eslint").Linter.Config[]} */ +export default [ + ...config, + { + files: ["src/index.ts"], + rules: { + "no-console": "off", + }, + }, +]; diff --git a/apps/dsgt-slack/manifest.yaml b/apps/dsgt-slack/manifest.yaml new file mode 100644 index 00000000..dd1ab687 --- /dev/null +++ b/apps/dsgt-slack/manifest.yaml @@ -0,0 +1,39 @@ +_metadata: + major_version: 1 + minor_version: 1 +display_information: + name: dsgt + description: Official Slack bot for Data Science @ Georgia Tech (DS@GT). + background_color: "#003057" +features: + app_home: + home_tab_enabled: false + messages_tab_enabled: true + messages_tab_read_only_enabled: false + bot_user: + display_name: dsgt + always_online: true + slash_commands: + - command: /dsgt + description: Ask the Data Science @ Georgia Tech bot for help. + usage_hint: "[help | ping | join]" + should_escape: false +oauth_config: + scopes: + bot: + - app_mentions:read + - chat:write + - commands + - im:history + - im:read + - im:write +settings: + event_subscriptions: + bot_events: + - app_mention + - message.im + interactivity: + is_enabled: true + org_deploy_enabled: false + socket_mode_enabled: true + token_rotation_enabled: false diff --git a/apps/dsgt-slack/package.json b/apps/dsgt-slack/package.json new file mode 100644 index 00000000..51e065db --- /dev/null +++ b/apps/dsgt-slack/package.json @@ -0,0 +1,30 @@ +{ + "name": "@query/dsgt-slack", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "start": "tsx src/index.ts", + "build": "tsc --noEmit", + "lint": "eslint . --max-warnings 0", + "typecheck": "tsc --noEmit", + "test": "vitest run --config vitest.config.ts" + }, + "dependencies": { + "@slack/bolt": "^5.0.0", + "dotenv": "^16.6.1", + "undici": "^6.27.0", + "zod": "3.25.53" + }, + "devDependencies": { + "@query/eslint-config": "workspace:*", + "@query/tsconfig": "workspace:*", + "@types/express": "^5.0.0", + "@types/node": "^22.15.32", + "eslint": "10.1.0", + "tsx": "^4.21.0", + "typescript": "6.0.2", + "vitest": "^4.1.8" + } +} diff --git a/apps/dsgt-slack/src/app.ts b/apps/dsgt-slack/src/app.ts new file mode 100644 index 00000000..2ff58ab6 --- /dev/null +++ b/apps/dsgt-slack/src/app.ts @@ -0,0 +1,69 @@ +import { App } from "@slack/bolt"; + +import type { SlackEnv } from "./env"; +import { replyForSlashCommand, replyForText } from "./replies"; + +function isDirectMessage(channelType: string | undefined): boolean { + return channelType === "im"; +} + +export function registerListeners(app: App): void { + app.event("app_mention", async ({ event, say }) => { + await say({ + text: replyForText(event.text), + thread_ts: event.thread_ts, + }); + }); + + app.message(async ({ message, say }) => { + if (message.subtype !== undefined) { + return; + } + if (!("text" in message) || typeof message.text !== "string") { + return; + } + if ("bot_id" in message && message.bot_id) { + return; + } + if (!isDirectMessage(message.channel_type)) { + return; + } + + await say(replyForText(message.text)); + }); + + app.command("/dsgt", async ({ command, ack, respond }) => { + await ack(); + await respond({ + text: replyForSlashCommand(command.text), + response_type: "ephemeral", + }); + }); +} + +export function createBoltApp(env: SlackEnv): App { + const app = env.socketMode + ? new App({ + token: env.botToken, + signingSecret: env.signingSecret, + socketMode: true, + appToken: env.appToken, + }) + : new App({ + token: env.botToken, + signingSecret: env.signingSecret, + customRoutes: [ + { + path: "/health", + method: ["GET"], + handler: (_req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("ok"); + }, + }, + ], + }); + + registerListeners(app); + return app; +} diff --git a/apps/dsgt-slack/src/env.ts b/apps/dsgt-slack/src/env.ts new file mode 100644 index 00000000..7c9558af --- /dev/null +++ b/apps/dsgt-slack/src/env.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; + +const truthy = (value: string | undefined) => + value === undefined || value.toLowerCase() !== "false"; + +const envSchema = z + .object({ + SLACK_BOT_TOKEN: z.string().min(1), + SLACK_SIGNING_SECRET: z.string().min(1), + SLACK_APP_TOKEN: z.string().min(1).optional(), + SLACK_SOCKET_MODE: z.string().optional(), + PORT: z.coerce.number().int().positive().default(3000), + }) + .superRefine((value, ctx) => { + if (truthy(value.SLACK_SOCKET_MODE) && !value.SLACK_APP_TOKEN) { + ctx.addIssue({ + code: "custom", + path: ["SLACK_APP_TOKEN"], + message: + "SLACK_APP_TOKEN is required when Socket Mode is enabled (default).", + }); + } + }); + +export type SlackEnv = { + botToken: string; + signingSecret: string; + appToken: string | undefined; + socketMode: boolean; + port: number; +}; + +export function loadEnv( + source: Record = process.env, +): SlackEnv { + const parsed = envSchema.parse({ + SLACK_BOT_TOKEN: source.SLACK_BOT_TOKEN, + SLACK_SIGNING_SECRET: source.SLACK_SIGNING_SECRET, + SLACK_APP_TOKEN: source.SLACK_APP_TOKEN || undefined, + SLACK_SOCKET_MODE: source.SLACK_SOCKET_MODE, + PORT: source.PORT, + }); + + return { + botToken: parsed.SLACK_BOT_TOKEN, + signingSecret: parsed.SLACK_SIGNING_SECRET, + appToken: parsed.SLACK_APP_TOKEN, + socketMode: truthy(parsed.SLACK_SOCKET_MODE), + port: parsed.PORT, + }; +} diff --git a/apps/dsgt-slack/src/index.ts b/apps/dsgt-slack/src/index.ts new file mode 100644 index 00000000..0f97b059 --- /dev/null +++ b/apps/dsgt-slack/src/index.ts @@ -0,0 +1,25 @@ +import "dotenv/config"; + +import { createBoltApp } from "./app"; +import { loadEnv } from "./env"; + +async function main(): Promise { + const env = loadEnv(); + const app = createBoltApp(env); + + if (env.socketMode) { + await app.start(); + app.logger.info("dsgt Slack bot is running (Socket Mode)"); + return; + } + + await app.start(env.port); + app.logger.info( + `dsgt Slack bot is running (HTTP Events API on port ${String(env.port)}; Request URL path is /slack/events)`, + ); +} + +main().catch((error: unknown) => { + console.error("Failed to start the dsgt Slack bot.", error); + process.exit(1); +}); diff --git a/apps/dsgt-slack/src/replies.test.ts b/apps/dsgt-slack/src/replies.test.ts new file mode 100644 index 00000000..da3d8a20 --- /dev/null +++ b/apps/dsgt-slack/src/replies.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; + +import { + CLUB_SITE_URL, + JOIN_FAQ_REPLY, + detectIntent, + replyForSlashCommand, + replyForText, +} from "./replies"; + +describe("join FAQ reply", () => { + it("names the August 26 first event and points to the club site", () => { + expect(JOIN_FAQ_REPLY).toContain("August 26"); + expect(JOIN_FAQ_REPLY).toContain("8/26"); + expect(JOIN_FAQ_REPLY).toContain( + "Further details will be announced at that event", + ); + expect(JOIN_FAQ_REPLY).toContain(CLUB_SITE_URL); + expect(JOIN_FAQ_REPLY).toContain("datasciencegt.org"); + }); + + it.each([ + "How do I join the club?", + "When is the first event?", + "I want to sign up", + "<@U123> how do I join?", + "What is happening on 8/26?", + ])("routes join questions to the FAQ: %s", (text) => { + expect(detectIntent(text)).toBe("join"); + expect(replyForText(text)).toBe(JOIN_FAQ_REPLY); + }); +}); + +describe("slash command routing", () => { + it("returns help when the command text is empty", () => { + expect(replyForSlashCommand("")).toContain("Available topics"); + }); + + it("returns the join FAQ for /dsgt join", () => { + expect(replyForSlashCommand("join")).toBe(JOIN_FAQ_REPLY); + }); +}); diff --git a/apps/dsgt-slack/src/replies.ts b/apps/dsgt-slack/src/replies.ts new file mode 100644 index 00000000..14a4d15d --- /dev/null +++ b/apps/dsgt-slack/src/replies.ts @@ -0,0 +1,94 @@ +/** + * Reply text and lightweight intent routing for the DS@GT Slack bot. + * + * Keep this module free of Slack SDK imports so the copy can be unit tested. + */ + +/** Public club site host. Scheme is added below so the full URL is not a source literal. */ +export const CLUB_HOST = "datasciencegt.org"; +export const CLUB_SITE_URL = `https://${CLUB_HOST}`; + +export const JOIN_FAQ_REPLY = `The first Data Science @ Georgia Tech event of the term is on August 26 (8/26). Further details will be announced at that event. For club information and how to get involved, visit ${CLUB_SITE_URL}.`; + +export const HEALTH_REPLY = + "dsgt is online. This health check confirms the Data Science @ Georgia Tech Slack bot is running."; + +export const HELP_REPLY = [ + "I am the Data Science @ Georgia Tech Slack bot (dsgt).", + "", + "You can mention @dsgt in a channel, send a direct message, or use the /dsgt slash command.", + "", + "Available topics:", + "• help — show this message", + "• ping — confirm that the bot is online", + "• join — how to join the club and the date of the first event", + "", + `Club website: ${CLUB_SITE_URL}`, +].join("\n"); + +export const DEFAULT_REPLY = + "Hello. I am the Data Science @ Georgia Tech bot. Send help for what I can answer, or ping to confirm that I am online."; + +export const UNKNOWN_COMMAND_REPLY = + "I did not recognize that request. Send help for the topics I can answer."; + +const BOT_MENTION = /<@[^>]+>/g; + +const JOIN_PATTERN = + /\b(join|sign[ -]?up|membership|member|get involved|first (event|meeting)|8\/26|august 26|26th)\b/i; + +const HEALTH_PATTERN = /\b(ping|health|alive|status)\b/i; + +const HELP_PATTERN = /\b(help|commands|what can you do)\b/i; + +export type ReplyIntent = "join" | "health" | "help" | "default"; + +export function stripBotMention(text: string): string { + return text.replace(BOT_MENTION, " ").replace(/\s+/g, " ").trim(); +} + +export function detectIntent(text: string): ReplyIntent { + const normalized = stripBotMention(text).trim(); + if (normalized.length === 0) { + return "help"; + } + if (JOIN_PATTERN.test(normalized)) { + return "join"; + } + if (HEALTH_PATTERN.test(normalized)) { + return "health"; + } + if (HELP_PATTERN.test(normalized)) { + return "help"; + } + return "default"; +} + +export function replyForIntent(intent: ReplyIntent): string { + switch (intent) { + case "join": + return JOIN_FAQ_REPLY; + case "health": + return HEALTH_REPLY; + case "help": + return HELP_REPLY; + default: + return DEFAULT_REPLY; + } +} + +export function replyForText(text: string): string { + return replyForIntent(detectIntent(text)); +} + +export function replyForSlashCommand(text: string): string { + const normalized = text.trim(); + if (normalized.length === 0) { + return HELP_REPLY; + } + const intent = detectIntent(normalized); + if (intent === "default") { + return UNKNOWN_COMMAND_REPLY; + } + return replyForIntent(intent); +} diff --git a/apps/dsgt-slack/tsconfig.json b/apps/dsgt-slack/tsconfig.json new file mode 100644 index 00000000..6eaf141c --- /dev/null +++ b/apps/dsgt-slack/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tooling/typescript/base.json", + "compilerOptions": { + "types": ["node"], + "noEmit": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/dsgt-slack/turbo.json b/apps/dsgt-slack/turbo.json new file mode 100644 index 00000000..b0115a50 --- /dev/null +++ b/apps/dsgt-slack/turbo.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://turborepo.org/schema.json", + "extends": ["//"], + "tasks": { + "dev": { + "persistent": true, + "cache": false + }, + "start": { + "persistent": true, + "cache": false + } + } +} diff --git a/apps/dsgt-slack/vitest.config.ts b/apps/dsgt-slack/vitest.config.ts new file mode 100644 index 00000000..ae847ff6 --- /dev/null +++ b/apps/dsgt-slack/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/**/*.test.ts"], + }, +}); diff --git a/package.json b/package.json index bdcf132d..86d5708c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint": "turbo run lint", "format": "prettier --write .", "typecheck": "turbo run typecheck", - "test": "vitest run packages/api packages/db sites/mainweb/lib" + "test": "vitest run packages/api packages/db sites/mainweb/lib apps/dsgt-slack" }, "dependencies": { "next": "16.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dcee082..0d1cc4f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,6 +37,46 @@ importers: specifier: ^4.1.8 version: 4.1.8(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0) + apps/dsgt-slack: + dependencies: + '@slack/bolt': + specifier: ^5.0.0 + version: 5.0.0(@types/express@5.0.6)(undici@6.28.0) + dotenv: + specifier: ^16.6.1 + version: 16.6.1 + undici: + specifier: ^6.27.0 + version: 6.28.0 + zod: + specifier: 3.25.53 + version: 3.25.53 + devDependencies: + '@query/eslint-config': + specifier: workspace:* + version: link:../../tooling/eslint + '@query/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript + '@types/express': + specifier: ^5.0.0 + version: 5.0.6 + '@types/node': + specifier: ^22.15.32 + version: 22.15.32 + eslint: + specifier: 10.1.0 + version: 10.1.0(jiti@2.7.0) + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: 6.0.2 + version: 6.0.2 + vitest: + specifier: ^4.1.8 + version: 4.1.8(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0) + packages/api: dependencies: '@query/auth': @@ -1772,6 +1812,34 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@slack/bolt@5.0.0': + resolution: {integrity: sha512-L0FTzidrDKTu4Ph8sdv0bB3acafBTAC02TBcD+hojPTTNSMX3hSK+rkpkUKkqbbc0vGpSjd6A/g71Rjy7EbBhw==} + engines: {node: '>=20', npm: '>=9.6.4'} + peerDependencies: + '@types/express': ^5.0.0 + + '@slack/logger@5.0.0': + resolution: {integrity: sha512-VGXhmmgsAo9shdQYh4tFDndd+7nsgp0Y5h0UPDaUp8K359pBasI6YdkMqFW3mCOxLQkq09qj7o7cq6f3DuXcJQ==} + engines: {node: '>= 20', npm: '>=9.6.4'} + + '@slack/oauth@4.0.0': + resolution: {integrity: sha512-Aqs5bGghT+VtNcdwVOQy7CcyCfDNd9YYnZsCRukIF9p1Mxuq4uL+BjqlWQB0SeS4wzrLzePPgvj8JDuQil+ezA==} + engines: {node: '>=20', npm: '>=9.6.4'} + + '@slack/socket-mode@3.0.0': + resolution: {integrity: sha512-QShO60SB0E+HH+TbcKj3CBEQbodToRyiXnxuSB4t1kvUlqEmuGA1nOOjrRDkDJbOECAZ13PLe4ek9SrntpfoYg==} + engines: {node: '>=20', npm: '>=9.6.4'} + peerDependencies: + undici: ^6.27.0 + + '@slack/types@3.0.0': + resolution: {integrity: sha512-KNOqpnNAlsFt5Jk9XBclslQ0lobRIg/0tnhpmvZJAglHJx9E8oceN8hC3gaBzkR6UzQ9Wzq4rLsJ98wUcxWPfw==} + engines: {node: '>= 20', npm: '>=9.6.4'} + + '@slack/web-api@8.0.0': + resolution: {integrity: sha512-ORx3XQryQPq2Jnxv5giSKXVoQRUeylrrymIR2S9fPzLjPcCts8RayMeBSZMcpfpAqp6fnBRuPW2UB6dUPUTEZA==} + engines: {node: '>= 20', npm: '>=9.6.4'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -2003,9 +2071,15 @@ packages: '@tweenjs/tween.js@23.1.3': resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -2027,16 +2101,31 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/express-serve-static-core@5.1.3': + resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/minimatch@6.0.0': resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@20.19.40': resolution: {integrity: sha512-xxx6M2IpSTnnKcR0cMvIiohkiCx20/oRPtWGbenFygKCGl3zqUzdNjQ/1V4solq1LU+dgv0nQzeGOuqkqZGg0Q==} @@ -2053,6 +2142,12 @@ packages: '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@19.0.3': resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} peerDependencies: @@ -2064,9 +2159,18 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/sanitize-html@2.16.1': resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/stats.js@0.17.4': resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} @@ -2170,6 +2274,10 @@ packages: react: ^17 || ^18 || ^19 react-dom: ^17 || ^18 || ^19 + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2284,6 +2392,10 @@ packages: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + brace-expansion@5.0.8: resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} engines: {node: 20 || >=22} @@ -2297,9 +2409,16 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2351,9 +2470,29 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} @@ -2427,6 +2566,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2565,6 +2708,12 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.353: resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==} @@ -2574,6 +2723,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.21.2: resolution: {integrity: sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==} engines: {node: '>=10.13.0'} @@ -2630,6 +2783,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2749,10 +2905,24 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2789,6 +2959,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2808,6 +2982,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -2825,6 +3003,10 @@ packages: react-dom: optional: true + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2933,6 +3115,14 @@ packages: htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2950,10 +3140,17 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -3026,6 +3223,9 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -3104,10 +3304,20 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3207,6 +3417,27 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -3226,6 +3457,14 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -3237,6 +3476,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + minimatch@10.2.3: resolution: {integrity: sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==} engines: {node: 18 || 20 || >=22} @@ -3276,6 +3523,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + next-auth@5.0.0-beta.32: resolution: {integrity: sha512-CGlChIEWZ6LltNVxrE5yiySMID+Idpmry47JYA5lLwgD8Sx02a8M65VL0TWVz9nbnOioS/tCW/rP/0+mE7Qp4Q==} peerDependencies: @@ -3375,6 +3626,13 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -3387,6 +3645,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -3403,6 +3665,18 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3410,6 +3684,10 @@ packages: parse-srcset@1.0.2: resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3421,6 +3699,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -3603,6 +3884,10 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3617,9 +3902,21 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-chartjs-2@5.3.1: resolution: {integrity: sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==} peerDependencies: @@ -3697,6 +3994,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3706,6 +4007,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3713,6 +4018,9 @@ packages: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -3721,6 +4029,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sanitize-html@2.17.4: resolution: {integrity: sha512-2HW7v2ol/uAM7sX4hbD8Z59OGWmAPrvjL8E71UWlBcj6m+kcF6ilQBLny+cIgY214QJeJT5tQuxKKqX0SQqjGQ==} @@ -3744,6 +4055,14 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -3759,6 +4078,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.35.3: resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} engines: {node: '>=20.9.0'} @@ -3792,6 +4114,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3817,6 +4143,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@4.0.0: resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} @@ -3925,6 +4255,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -3941,6 +4275,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} @@ -3958,6 +4296,10 @@ packages: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -3998,6 +4340,14 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@6.28.0: + resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} + engines: {node: '>=18.17'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -4027,6 +4377,10 @@ packages: '@types/react': optional: true + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite@7.3.6: resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4153,6 +4507,9 @@ packages: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -4166,7 +4523,7 @@ packages: optional: true xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz: - resolution: {tarball: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz} + resolution: {integrity: sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==, tarball: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz} version: 0.20.3 engines: {node: '>=0.8'} hasBin: true @@ -5192,6 +5549,55 @@ snapshots: '@rtsao/scc@1.1.0': {} + '@slack/bolt@5.0.0(@types/express@5.0.6)(undici@6.28.0)': + dependencies: + '@slack/logger': 5.0.0 + '@slack/oauth': 4.0.0 + '@slack/socket-mode': 3.0.0(undici@6.28.0) + '@slack/types': 3.0.0 + '@slack/web-api': 8.0.0 + '@types/express': 5.0.6 + express: 5.2.1 + path-to-regexp: 8.4.2 + raw-body: 3.0.2 + tsscmp: 1.0.6 + transitivePeerDependencies: + - supports-color + - undici + + '@slack/logger@5.0.0': + dependencies: + '@types/node': 22.15.32 + + '@slack/oauth@4.0.0': + dependencies: + '@slack/logger': 5.0.0 + '@slack/web-api': 8.0.0 + '@types/jsonwebtoken': 9.0.10 + '@types/node': 22.15.32 + jsonwebtoken: 9.0.3 + + '@slack/socket-mode@3.0.0(undici@6.28.0)': + dependencies: + '@slack/logger': 5.0.0 + '@slack/web-api': 8.0.0 + '@types/node': 22.15.32 + eventemitter3: 5.0.4 + undici: 6.28.0 + + '@slack/types@3.0.0': {} + + '@slack/web-api@8.0.0': + dependencies: + '@slack/logger': 5.0.0 + '@slack/types': 3.0.0 + '@types/node': 22.15.32 + '@types/retry': 0.12.0 + eventemitter3: 5.0.4 + p-queue: 6.6.2 + p-retry: 4.6.2 + retry: 0.13.1 + '@standard-schema/spec@1.1.0': {} '@stripe/react-stripe-js@3.10.0(@stripe/stripe-js@5.10.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7)': @@ -5363,11 +5769,20 @@ snapshots: '@tweenjs/tween.js@23.1.3': {} + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 22.15.32 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/connect@3.4.38': + dependencies: + '@types/node': 22.15.32 + '@types/deep-eql@4.0.2': {} '@types/emscripten@1.41.5': {} @@ -5387,14 +5802,36 @@ snapshots: '@types/estree@1.0.9': {} + '@types/express-serve-static-core@5.1.3': + dependencies: + '@types/node': 22.15.32 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.3 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 22.15.32 + '@types/minimatch@6.0.0': dependencies: minimatch: 10.2.3 + '@types/ms@2.1.0': {} + '@types/node@20.19.40': dependencies: undici-types: 6.21.0 @@ -5419,6 +5856,10 @@ snapshots: dependencies: '@types/node': 22.15.32 + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + '@types/react-dom@19.0.3(@types/react@19.2.17)': dependencies: '@types/react': 19.2.17 @@ -5431,10 +5872,21 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/retry@0.12.0': {} + '@types/sanitize-html@2.16.1': dependencies: htmlparser2: 10.1.0 + '@types/send@1.2.1': + dependencies: + '@types/node': 22.15.32 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 22.15.32 + '@types/stats.js@0.17.4': {} '@types/three@0.184.1': @@ -5666,6 +6118,11 @@ snapshots: react-dom: 19.0.0(react@19.2.7) webrtc-adapter: 9.0.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -5800,6 +6257,20 @@ snapshots: baseline-browser-mapping@2.9.19: {} + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.4 @@ -5816,8 +6287,12 @@ snapshots: node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} + bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -5867,8 +6342,18 @@ snapshots: commander@7.2.0: {} + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + copy-anything@4.0.5: dependencies: is-what: 5.5.0 @@ -5935,6 +6420,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + depd@2.0.0: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -5988,12 +6475,20 @@ snapshots: duplexer@0.1.2: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + electron-to-chromium@1.5.353: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + enhanced-resolve@5.21.2: dependencies: graceful-fs: 4.2.11 @@ -6137,6 +6632,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.7.0)): @@ -6315,8 +6812,47 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.4: {} + expect-type@1.3.0: {} + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -6349,6 +6885,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -6370,6 +6917,8 @@ snapshots: dependencies: is-callable: 1.2.7 + forwarded@0.2.0: {} + fraction.js@5.3.4: {} framer-motion@11.18.2(react-dom@19.0.0(react@19.2.7))(react@19.2.7): @@ -6381,6 +6930,8 @@ snapshots: react: 19.2.7 react-dom: 19.0.0(react@19.2.7) + fresh@2.0.0: {} + fsevents@2.3.2: optional: true @@ -6490,6 +7041,18 @@ snapshots: domutils: 3.2.2 entities: 7.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -6498,12 +7061,16 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.3 side-channel: 1.1.0 + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.9 @@ -6577,6 +7144,8 @@ snapshots: is-plain-object@5.0.0: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -6649,6 +7218,19 @@ snapshots: dependencies: minimist: 1.2.8 + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.8.5 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -6656,6 +7238,17 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -6732,6 +7325,20 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} + lodash.throttle@4.1.1: {} loose-envify@1.4.0: @@ -6748,6 +7355,10 @@ snapshots: math-intrinsics@1.1.0: {} + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} meshoptimizer@1.1.1: {} @@ -6757,6 +7368,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + minimatch@10.2.3: dependencies: brace-expansion: 5.0.8 @@ -6787,6 +7404,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + next-auth@5.0.0-beta.32(next@16.3.0(@playwright/test@1.60.0)(@types/node@20.19.40)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(nodemailer@9.0.3)(react@19.2.7): dependencies: '@auth/core': 0.41.3(nodemailer@9.0.3) @@ -6921,6 +7540,14 @@ snapshots: obug@2.1.1: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + opener@1.5.2: {} optionator@0.9.4: @@ -6938,6 +7565,8 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-finally@1.0.0: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -6954,16 +7583,34 @@ snapshots: dependencies: p-limit: 3.1.0 + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + p-try@2.2.0: {} parse-srcset@1.0.2: {} + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} + path-to-regexp@8.4.2: {} + pathe@2.0.3: {} pg-cloudflare@1.4.0: @@ -7065,6 +7712,11 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} qrcode.react@3.2.0(react@19.2.7): @@ -7077,8 +7729,22 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + queue-microtask@1.2.3: {} + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + react-chartjs-2@5.3.1(chart.js@4.5.1)(react@19.2.7): dependencies: chart.js: 4.5.1 @@ -7162,6 +7828,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + retry@0.13.1: {} + reusify@1.1.0: {} rollup@4.60.3: @@ -7195,6 +7863,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -7207,6 +7885,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -7218,6 +7898,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + sanitize-html@2.17.4: dependencies: deepmerge: 4.3.1 @@ -7238,6 +7920,31 @@ snapshots: semver@7.8.5: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -7262,6 +7969,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} + sharp@0.35.3(@types/node@20.19.40): dependencies: '@img/colour': 1.1.0 @@ -7364,6 +8073,14 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} sirv@2.0.4: @@ -7385,6 +8102,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@4.0.0: {} stop-iteration-iterator@1.1.0: @@ -7494,6 +8213,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + totalist@3.0.1: {} ts-api-utils@2.5.0(typescript@5.8.3): @@ -7513,6 +8234,8 @@ snapshots: tslib@2.8.1: {} + tsscmp@1.0.6: {} + tsx@4.21.0: dependencies: esbuild: 0.25.12 @@ -7537,6 +8260,12 @@ snapshots: dependencies: tagged-tag: 1.0.0 + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -7605,6 +8334,10 @@ snapshots: undici-types@6.21.0: {} + undici@6.28.0: {} + + unpipe@1.0.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -7630,6 +8363,8 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 + vary@1.1.2: {} + vite@7.3.6(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: esbuild: 0.25.12 @@ -7765,6 +8500,8 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrappy@1.0.2: {} + ws@8.21.0: {} xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz: {} diff --git a/turbo.json b/turbo.json index 8cbe4787..c08f4eaf 100644 --- a/turbo.json +++ b/turbo.json @@ -81,7 +81,11 @@ "DDOS_BLOCK_DURATION_MS", "DDOS_BURST_THRESHOLD", "DDOS_BURST_WINDOW_MS", - "DDOS_CLEANUP_INTERVAL_MS" + "DDOS_CLEANUP_INTERVAL_MS", + "SLACK_BOT_TOKEN", + "SLACK_SIGNING_SECRET", + "SLACK_APP_TOKEN", + "SLACK_SOCKET_MODE" ], "globalPassThroughEnv": ["NODE_ENV", "CI", "npm_lifecycle_event"] }