diff --git a/README.md b/README.md index 78acdc68..073e7887 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Two Next.js sites share one Postgres database and four internal packages. Club m | --- | --- | --- | | `sites/mainweb` | `web` | Public club site + authenticated portal (App Hosting) | | `sites/hacklytics2027` | `hacklytics2027` | Hacklytics 2027 marketing site, static export (Firebase Hosting) | +| `apps/dsgt-slack` | `@query/dsgt-slack` | `@dsgt` Slack bot (HTTP webhook on mainweb in production) | | `packages/api` | `@query/api` | tRPC routers, middleware, pricing | | `packages/auth` | `@query/auth` | NextAuth (Google, GitHub, email codes) | | `packages/db` | `@query/db` | Drizzle schema, client, membership rules | diff --git a/apphosting.yaml b/apphosting.yaml index b987633f..0055fee0 100644 --- a/apphosting.yaml +++ b/apphosting.yaml @@ -57,6 +57,19 @@ env: secret: projects/672446353769/secrets/NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY - variable: STRIPE_WEBHOOK_SECRET secret: projects/672446353769/secrets/STRIPE_WEBHOOK_SECRET + # Slack Events API for @dsgt (HTTP, not Socket Mode). Create these secrets + # once before the deploy that references them, or App Hosting cannot start: + # gcloud secrets create SLACK_BOT_TOKEN --project=dsgt-website + # gcloud secrets create SLACK_SIGNING_SECRET --project=dsgt-website + # firebase apphosting:secrets:grantaccess SLACK_BOT_TOKEN --backend query + # firebase apphosting:secrets:grantaccess SLACK_SIGNING_SECRET --backend query + # Steps and Request URL: apps/dsgt-slack/README.md + - variable: SLACK_BOT_TOKEN + secret: projects/672446353769/secrets/SLACK_BOT_TOKEN + - variable: SLACK_SIGNING_SECRET + secret: projects/672446353769/secrets/SLACK_SIGNING_SECRET + - variable: SLACK_SOCKET_MODE + value: "false" - variable: NODE_ENV value: production # Consumer Gmail, which caps around 500 recipients a day — shared between diff --git a/apps/dsgt-slack/.env.example b/apps/dsgt-slack/.env.example new file mode 100644 index 00000000..a6ccd24a --- /dev/null +++ b/apps/dsgt-slack/.env.example @@ -0,0 +1,22 @@ +# 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 only for local Socket Mode. Create a token with the connections:write +# scope. Not used in production (HTTP Events API on Firebase App Hosting). +SLACK_APP_TOKEN=xapp-your-app-token + +# Local default is Socket Mode. Production App Hosting sets this to false. +# SLACK_APP_TOKEN is not required when this is false. +SLACK_SOCKET_MODE=true + +# Used only when running this package's local HTTP receiver +# (SLACK_SOCKET_MODE=false). Production Request URL is the App Hosting +# origin (AUTH_URL, host datasciencegt.org) plus /api/webhooks/slack. +PORT=3000 diff --git a/apps/dsgt-slack/README.md b/apps/dsgt-slack/README.md new file mode 100644 index 00000000..ed56d007 --- /dev/null +++ b/apps/dsgt-slack/README.md @@ -0,0 +1,209 @@ +# 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 a +third-party 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. + +Production traffic is **HTTP Events API** on the existing Firebase App Hosting +backend (`sites/mainweb` / Cloud Run). There is no second Cloud Run service and +no Socket Mode process in production. + +## Grok Bot (the LLM / MCP teammate) + +This Firebase app is **not** Grok Bot. It is a small FAQ process: help, ping, +and the August 26 join answer. It cannot use your Grok Bot computer, Google +connectors, or MCP tools. + +If club Slack should talk to **your Grok Bot** (the Yodo pattern: people DM +`@dsgt`, and the teammate uses Slack, Drive, Slides, Forms, Gmail), do that in +the Grok Bot app, not here: + +1. Open Grok Bot and open the teammate you want as `@dsgt`. +2. Connect **Slack** to the **DS@GT club workspace** (not Campus Leads). +3. When Slack asks to install the app, click **Allow** as a workspace admin. +4. Add Google Workspace connectors there (Drive / Slides / Forms). MCP for + those products lives on Grok Bot. +5. Name the Slack bot user `dsgt` so it matches the club identity. + +You can keep this FAQ webhook as a fallback, or skip installing it if Grok Bot +is the only `@dsgt` in the workspace. Do not run both against the same bot +tokens or they will fight over events. + +## 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 **off**. The manifest does **not** declare + Event Subscriptions, Interactivity, or `/dsgt` — Slack rejects those without + a Request URL or Socket Mode (`invalid_manifest`). You can add them in the + app settings after `{AUTH_URL}/api/webhooks/slack` is live. +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-...`). You will put this in GCP + Secret Manager as `SLACK_BOT_TOKEN` (and in `apps/dsgt-slack/.env` only if + you run the bot locally). +3. Open **Basic Information** and copy the **Signing Secret**. That is + `SLACK_SIGNING_SECRET`. + +Do **not** commit `.env`, tokens, or signing secrets. `SLACK_APP_TOKEN` is not +required in production. + +## Production Request URL (Firebase App Hosting) + +Slack must POST to the **Firebase App Hosting / Cloud Run** URL for `sites/mainweb` +(workspace `web`). That origin is `{AUTH_URL}` — the same public +origin as `AUTH_URL` / `NEXTAUTH_URL` in [`apphosting.yaml`](../../apphosting.yaml). + +**Use this Request URL everywhere** (Event Subscriptions, Slash Commands +`/dsgt`, Interactivity & Shortcuts). Take the `AUTH_URL` / `NEXTAUTH_URL` +value from [`apphosting.yaml`](../../apphosting.yaml) (the App Hosting origin +for the club site, host `datasciencegt.org`) and append the path: + +```text +{AUTH_URL}/api/webhooks/slack +``` + +Set those three URLs in the Slack app settings **after** the App Hosting +deploy that includes this route is live (Event Subscriptions, Slash Commands +`/dsgt`, Interactivity). They are not in `manifest.yaml` because Slack will +not create the app with those features unless a Request URL or Socket Mode is +set. Slack will POST a `url_verification` challenge; the Next.js route +answers it. + +The Next.js route is +`sites/mainweb/app/(portal)/api/webhooks/stripe`’s neighbor: +`sites/mainweb/app/(portal)/api/webhooks/slack/route.ts`. `proxy.ts` already +leaves `/api/webhooks/*` alone, same as Stripe. + +### App Hosting vs Firebase Hosting + +| Surface | What it is | Slack | +| --- | --- | --- | +| **Firebase App Hosting** | Next.js on Cloud Run, `apphosting.yaml`, `sites/mainweb` | **Yes — this is the API** | +| **Firebase Hosting** site `dsgt-website` | Static `sites/mainweb/out/` | **No.** That site cannot run the webhook | + +If Event Subscriptions shows a verification failure, the Request URL is almost +certainly pointing at Hosting `out/` (or the wrong host), not App Hosting. + +After the App Hosting deploy that includes this route is live, open **Event +Subscriptions** and click **Retry** / save so Slack can complete the +`url_verification` challenge. + +Reinstall the app if Slack asks you to after changing URLs or scopes. + +## Secret Manager (one-time) + +GCP project: `dsgt-website` (number `672446353769`). App Hosting backend name: +`query`. [`apphosting.yaml`](../../apphosting.yaml) maps: + +| Process env | Secret | +| --- | --- | +| `SLACK_BOT_TOKEN` | `projects/672446353769/secrets/SLACK_BOT_TOKEN` | +| `SLACK_SIGNING_SECRET` | `projects/672446353769/secrets/SLACK_SIGNING_SECRET` | +| `SLACK_SOCKET_MODE` | literal `false` (not a secret) | + +`SLACK_APP_TOKEN` is **not** wired. Create the secrets before the deploy that +references them, or App Hosting will fail to start: + +```bash +gcloud config set project dsgt-website + +# Create empty secrets, then add a version from stdin (do not put tokens in the +# shell history file if you can avoid it — prefer a local file you delete). +gcloud secrets create SLACK_BOT_TOKEN --project=dsgt-website +gcloud secrets create SLACK_SIGNING_SECRET --project=dsgt-website + +printf '%s' 'xoxb-your-bot-token' | gcloud secrets versions add SLACK_BOT_TOKEN --data-file=- +printf '%s' 'your-signing-secret' | gcloud secrets versions add SLACK_SIGNING_SECRET --data-file=- + +firebase apphosting:secrets:grantaccess SLACK_BOT_TOKEN --backend query +firebase apphosting:secrets:grantaccess SLACK_SIGNING_SECRET --backend query +``` + +Replace the `printf` placeholders with the real values from the Slack app +settings. Never commit those values. + +## Run locally (Socket Mode) + +Socket Mode is a **local-only** option so you can develop without a public URL. +Production stays on HTTP. + +1. In the Slack app settings, turn **Socket Mode** on temporarily (or use a + separate development app). Generate an App-Level Token with + `connections:write` (`xapp-...`). +2. From the monorepo root: + +```bash +cp apps/dsgt-slack/.env.example apps/dsgt-slack/.env +# edit apps/dsgt-slack/.env with SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, +# SLACK_APP_TOKEN, and SLACK_SOCKET_MODE=true + +pnpm install +pnpm --filter @query/dsgt-slack dev +``` + +`pnpm dev` at the repo root also starts this package. If `.env` is missing, the +task stays idle so the rest of the monorepo can still boot. + +You can also hit the Next.js webhook locally while `web` is running +(`http://localhost:3001/api/webhooks/slack`) if you tunnel that URL to Slack. +That path is what production uses. + +### 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 | + +## 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. + +| Variable | Local Socket Mode | Production (App Hosting) | +| --- | --- | --- | +| `SLACK_BOT_TOKEN` | required | Secret Manager | +| `SLACK_SIGNING_SECRET` | required | Secret Manager | +| `SLACK_APP_TOKEN` | required | not used | +| `SLACK_SOCKET_MODE` | `true` (default) | `false` | 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..62ffdeea --- /dev/null +++ b/apps/dsgt-slack/manifest.yaml @@ -0,0 +1,29 @@ +_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 +oauth_config: + scopes: + bot: + - app_mentions:read + - chat:write + - chat:write.public + - commands + - im:history + - im:read + - im:write +settings: + org_deploy_enabled: false + socket_mode_enabled: false + token_rotation_enabled: false diff --git a/apps/dsgt-slack/package.json b/apps/dsgt-slack/package.json new file mode 100644 index 00000000..14a35b1f --- /dev/null +++ b/apps/dsgt-slack/package.json @@ -0,0 +1,36 @@ +{ + "name": "@query/dsgt-slack", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/replies.ts", + "./replies": "./src/replies.ts", + "./http": "./src/http.ts" + }, + "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", + "@slack/web-api": "^7.13.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.10" + } +} 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..398c8bb5 --- /dev/null +++ b/apps/dsgt-slack/src/env.ts @@ -0,0 +1,57 @@ +/** + * Env for the local Bolt process (Socket Mode or a local HTTP receiver). + * Production App Hosting does not call this — the Next.js webhook reads + * SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET at request time. + */ + +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/http.test.ts b/apps/dsgt-slack/src/http.test.ts new file mode 100644 index 00000000..2e926f68 --- /dev/null +++ b/apps/dsgt-slack/src/http.test.ts @@ -0,0 +1,255 @@ +import { createHmac } from "node:crypto"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { + SLACK_WEBHOOK_PATH, + handleSlackWebhook, + resetSlackEventDedupe, + verifySlackSignature, +} from "./http"; +import { CLUB_SITE_URL, JOIN_FAQ_REPLY } from "./replies"; + +const SIGNING_SECRET = "test-signing-secret"; +const FROZEN_TS = "1710000000"; +const FROZEN_NOW_MS = Number(FROZEN_TS) * 1000; + +function sign(rawBody: string, timestamp: string): string { + const digest = createHmac("sha256", SIGNING_SECRET) + .update(`v0:${timestamp}:${rawBody}`, "utf8") + .digest("hex"); + return `v0=${digest}`; +} + +function signedRequest(rawBody: string, contentType: string): Request { + const timestamp = String(Math.floor(Date.now() / 1000)); + return new Request(`${CLUB_SITE_URL}${SLACK_WEBHOOK_PATH}`, { + method: "POST", + headers: { + "content-type": contentType, + "x-slack-request-timestamp": timestamp, + "x-slack-signature": sign(rawBody, timestamp), + }, + body: rawBody, + }); +} + +describe("verifySlackSignature", () => { + it("accepts a matching v0 signature within the time window", () => { + const rawBody = `{"ok":true}`; + expect( + verifySlackSignature({ + signingSecret: SIGNING_SECRET, + signature: sign(rawBody, FROZEN_TS), + timestamp: FROZEN_TS, + rawBody, + nowMs: FROZEN_NOW_MS, + }), + ).toBe(true); + }); + + it("rejects a missing or wrong signature", () => { + const rawBody = `{"ok":true}`; + expect( + verifySlackSignature({ + signingSecret: SIGNING_SECRET, + signature: null, + timestamp: FROZEN_TS, + rawBody, + nowMs: FROZEN_NOW_MS, + }), + ).toBe(false); + expect( + verifySlackSignature({ + signingSecret: SIGNING_SECRET, + signature: "v0=deadbeef", + timestamp: FROZEN_TS, + rawBody, + nowMs: FROZEN_NOW_MS, + }), + ).toBe(false); + }); +}); + +describe("handleSlackWebhook", () => { + const env = { botToken: "xoxb-test", signingSecret: SIGNING_SECRET }; + + beforeEach(() => { + resetSlackEventDedupe(); + }); + + it("returns the url_verification challenge", async () => { + const rawBody = JSON.stringify({ + type: "url_verification", + challenge: "abc-challenge", + }); + const response = await handleSlackWebhook( + signedRequest(rawBody, "application/json"), + env, + ); + expect(response.status).toBe(200); + await expect(response.json()).resolves.toEqual({ + challenge: "abc-challenge", + }); + }); + + it("rejects unsigned requests", async () => { + const response = await handleSlackWebhook( + new Request(`${CLUB_SITE_URL}${SLACK_WEBHOOK_PATH}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ type: "url_verification", challenge: "nope" }), + }), + env, + ); + expect(response.status).toBe(400); + }); + + it("replies to an app_mention join question with the FAQ", async () => { + const postMessage = vi.fn().mockResolvedValue({}); + const rawBody = JSON.stringify({ + type: "event_callback", + event: { + type: "app_mention", + text: "<@U123> how do I join?", + channel: "C123", + thread_ts: "1710000000.000100", + }, + }); + + const response = await handleSlackWebhook( + signedRequest(rawBody, "application/json"), + env, + { chat: { postMessage } }, + ); + + expect(response.status).toBe(200); + expect(postMessage).toHaveBeenCalledWith({ + channel: "C123", + text: JOIN_FAQ_REPLY, + thread_ts: "1710000000.000100", + }); + }); + + it("replies to a DM join question with the FAQ", async () => { + const postMessage = vi.fn().mockResolvedValue({}); + const rawBody = JSON.stringify({ + type: "event_callback", + event: { + type: "message", + channel_type: "im", + text: "How do I join the club?", + channel: "D123", + }, + }); + + const response = await handleSlackWebhook( + signedRequest(rawBody, "application/json"), + env, + { chat: { postMessage } }, + ); + + expect(response.status).toBe(200); + expect(postMessage).toHaveBeenCalledWith({ + channel: "D123", + text: JOIN_FAQ_REPLY, + }); + }); + + it("acks /dsgt join with the FAQ as an ephemeral response", async () => { + const rawBody = "command=%2Fdsgt&text=join"; + const response = await handleSlackWebhook( + signedRequest(rawBody, "application/x-www-form-urlencoded"), + env, + { chat: { postMessage: vi.fn() } }, + ); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toEqual({ + response_type: "ephemeral", + text: JOIN_FAQ_REPLY, + }); + }); + + it("acks event_callback before chat.postMessage resolves", async () => { + let release: (value: unknown) => void = () => { + /* assigned below */ + }; + const postMessage = vi.fn( + () => + new Promise((resolve) => { + release = resolve; + }), + ); + const rawBody = JSON.stringify({ + type: "event_callback", + event_id: "EvACK", + event: { + type: "app_mention", + text: "<@U123> ping", + channel: "C123", + }, + }); + + const response = await handleSlackWebhook( + signedRequest(rawBody, "application/json"), + env, + { chat: { postMessage } }, + ); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toEqual({ ok: true }); + expect(postMessage).toHaveBeenCalledTimes(1); + release({}); + }); + + it("does not post twice for a retried event_id", async () => { + const postMessage = vi.fn().mockResolvedValue({}); + const rawBody = JSON.stringify({ + type: "event_callback", + event_id: "EvDUP", + event: { + type: "app_mention", + text: "<@U123> how do I join?", + channel: "C123", + }, + }); + const client = { chat: { postMessage } }; + + const first = await handleSlackWebhook( + signedRequest(rawBody, "application/json"), + env, + client, + ); + const retry = await handleSlackWebhook( + signedRequest(rawBody, "application/json"), + env, + client, + ); + + expect(first.status).toBe(200); + expect(retry.status).toBe(200); + expect(postMessage).toHaveBeenCalledTimes(1); + }); + + it("does not treat a slow postMessage rejection as invalid JSON", async () => { + const postMessage = vi.fn().mockRejectedValue(new Error("slack down")); + const rawBody = JSON.stringify({ + type: "event_callback", + event_id: "EvFAIL", + event: { + type: "app_mention", + text: "<@U123> ping", + channel: "C123", + }, + }); + + const response = await handleSlackWebhook( + signedRequest(rawBody, "application/json"), + env, + { chat: { postMessage } }, + ); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toEqual({ ok: true }); + }); +}); diff --git a/apps/dsgt-slack/src/http.ts b/apps/dsgt-slack/src/http.ts new file mode 100644 index 00000000..deb78952 --- /dev/null +++ b/apps/dsgt-slack/src/http.ts @@ -0,0 +1,253 @@ +/** + * HTTP Events API handler for production. + * + * Slack POSTs to the Firebase App Hosting (Cloud Run) URL. This module is Fetch + * Request/Response based so Next.js App Router can call it. It does not start a + * Bolt HTTP server — Socket Mode / Bolt remain a local-only process. + */ + +import { createHmac, timingSafeEqual } from "node:crypto"; +import { WebClient } from "@slack/web-api"; + +import { replyForSlashCommand, replyForText } from "./replies"; + +/** Stable Request URL path on mainweb. Use this in the Slack app settings. */ +export const SLACK_WEBHOOK_PATH = "/api/webhooks/slack"; + +const MAX_CLOCK_SKEW_SEC = 60 * 5; + +/** FIFO cap so a long-lived instance cannot grow without bound. */ +const MAX_SEEN_EVENTS = 500; +const seenEventIds = new Set(); + +export type SlackDefer = (work: Promise) => void; + +const defaultDefer: SlackDefer = (work) => { + void work.catch(() => { + /* postMessage failures must not turn an ACK into a 400 Slack retry. */ + }); +}; + +/** Test helper: event_id dedupe is process-local. */ +export function resetSlackEventDedupe(): void { + seenEventIds.clear(); +} + +function alreadySeenEvent(eventId: string): boolean { + if (seenEventIds.has(eventId)) { + return true; + } + seenEventIds.add(eventId); + if (seenEventIds.size > MAX_SEEN_EVENTS) { + const oldest = seenEventIds.values().next().value; + if (oldest !== undefined) { + seenEventIds.delete(oldest); + } + } + return false; +} + +export type SlackHttpEnv = { + botToken: string; + signingSecret: string; +}; + +export type SlackPoster = { + chat: { + postMessage: (args: { + channel: string; + text: string; + thread_ts?: string; + }) => Promise; + }; +}; + +function safeEqual(left: string, right: string): boolean { + const leftBuf = Buffer.from(left, "utf8"); + const rightBuf = Buffer.from(right, "utf8"); + if (leftBuf.length !== rightBuf.length) { + return false; + } + return timingSafeEqual(leftBuf, rightBuf); +} + +export function verifySlackSignature(options: { + signingSecret: string; + signature: string | null; + timestamp: string | null; + rawBody: string; + nowMs?: number; +}): boolean { + const { + signingSecret, + signature, + timestamp, + rawBody, + nowMs = Date.now(), + } = options; + + if (!signature || !timestamp || !/^[0-9]+$/.test(timestamp)) { + return false; + } + + const ageSec = Math.abs(nowMs / 1000 - Number(timestamp)); + if (ageSec > MAX_CLOCK_SKEW_SEC) { + return false; + } + + const digest = createHmac("sha256", signingSecret) + .update(`v0:${timestamp}:${rawBody}`, "utf8") + .digest("hex"); + + return safeEqual(`v0=${digest}`, signature); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +type SlackMessageEvent = { + type: string; + subtype?: string; + bot_id?: string; + text?: string; + channel?: string; + channel_type?: string; + thread_ts?: string; +}; + +function parseMessageEvent(value: unknown): SlackMessageEvent | null { + if (!isRecord(value) || typeof value.type !== "string") { + return null; + } + + return { + type: value.type, + subtype: typeof value.subtype === "string" ? value.subtype : undefined, + bot_id: typeof value.bot_id === "string" ? value.bot_id : undefined, + text: typeof value.text === "string" ? value.text : undefined, + channel: typeof value.channel === "string" ? value.channel : undefined, + channel_type: + typeof value.channel_type === "string" ? value.channel_type : undefined, + thread_ts: + typeof value.thread_ts === "string" ? value.thread_ts : undefined, + }; +} + +async function handleEvent( + event: SlackMessageEvent, + client: SlackPoster, +): Promise { + if (event.bot_id || event.subtype) { + return; + } + if (typeof event.text !== "string" || typeof event.channel !== "string") { + return; + } + + if (event.type === "app_mention") { + await client.chat.postMessage({ + channel: event.channel, + text: replyForText(event.text), + ...(event.thread_ts ? { thread_ts: event.thread_ts } : {}), + }); + return; + } + + if (event.type === "message" && event.channel_type === "im") { + await client.chat.postMessage({ + channel: event.channel, + text: replyForText(event.text), + }); + } +} + +async function handleJsonBody( + payload: unknown, + client: SlackPoster, + defer: SlackDefer, +): Promise { + if (!isRecord(payload) || typeof payload.type !== "string") { + return Response.json({ error: "Invalid payload" }, { status: 400 }); + } + + if (payload.type === "url_verification") { + const challenge = + typeof payload.challenge === "string" ? payload.challenge : ""; + return Response.json({ challenge }); + } + + if (payload.type === "event_callback") { + const eventId = + typeof payload.event_id === "string" ? payload.event_id : ""; + if (eventId && alreadySeenEvent(eventId)) { + return Response.json({ ok: true }); + } + const event = parseMessageEvent(payload.event); + if (event) { + // ACK first. Slack retries after ~3s; awaiting chat.postMessage here + // (especially after a cold start) duplicates the reply. + defer(handleEvent(event, client)); + } + return Response.json({ ok: true }); + } + + return Response.json({ ok: true }); +} + +function handleFormBody(rawBody: string): Response { + const params = new URLSearchParams(rawBody); + + if (params.get("ssl_check") === "1") { + return new Response("", { status: 200 }); + } + + if (params.has("payload")) { + return new Response("", { status: 200 }); + } + + const command = params.get("command"); + if (command) { + return Response.json({ + response_type: "ephemeral", + text: replyForSlashCommand(params.get("text") ?? ""), + }); + } + + return Response.json({ error: "Unknown Slack request" }, { status: 400 }); +} + +export async function handleSlackWebhook( + req: Request, + env: SlackHttpEnv, + client: SlackPoster = new WebClient(env.botToken), + defer: SlackDefer = defaultDefer, +): Promise { + const rawBody = await req.text(); + const valid = verifySlackSignature({ + signingSecret: env.signingSecret, + signature: req.headers.get("x-slack-signature"), + timestamp: req.headers.get("x-slack-request-timestamp"), + rawBody, + }); + + if (!valid) { + return Response.json({ error: "Invalid signature" }, { status: 400 }); + } + + const contentType = req.headers.get("content-type") ?? ""; + if ( + contentType.includes("application/json") || + rawBody.trimStart().startsWith("{") + ) { + let payload: unknown; + try { + payload = JSON.parse(rawBody) as unknown; + } catch { + return Response.json({ error: "Invalid JSON body" }, { status: 400 }); + } + return handleJsonBody(payload, client, defer); + } + + return handleFormBody(rawBody); +} diff --git a/apps/dsgt-slack/src/index.ts b/apps/dsgt-slack/src/index.ts new file mode 100644 index 00000000..88da53b5 --- /dev/null +++ b/apps/dsgt-slack/src/index.ts @@ -0,0 +1,38 @@ +import "dotenv/config"; + +import { createBoltApp } from "./app"; +import type { SlackEnv } from "./env"; +import { loadEnv } from "./env"; + +async function main(): Promise { + let env: SlackEnv; + try { + env = loadEnv(); + } catch { + console.warn( + "dsgt Slack bot skipped: set SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET in apps/dsgt-slack/.env to run locally (Socket Mode). Production traffic is handled by sites/mainweb at /api/webhooks/slack.", + ); + await new Promise(() => { + /* keep turbo `dev` from restarting this task */ + }); + return; + } + + const app = createBoltApp(env); + + if (env.socketMode) { + await app.start(); + app.logger.info("dsgt Slack bot is running (Socket Mode, local)"); + return; + } + + await app.start(env.port); + app.logger.info( + `dsgt Slack bot is running a local HTTP receiver on port ${String(env.port)} at /slack/events. Production uses Firebase App Hosting at /api/webhooks/slack.`, + ); +} + +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..5c247a88 --- /dev/null +++ b/apps/dsgt-slack/src/replies.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from "vitest"; + +import { + CLUB_SITE_URL, + JOIN_FAQ_REPLY, + detectIntent, + replyForSlashCommand, + replyForText, + stripBotMention, +} 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); + }); +}); + +describe("stripBotMention", () => { + it("removes a Slack user mention before the rest of the text", () => { + expect(stripBotMention("<@U123> how do I join?")).toBe("how do I join?"); + }); + + it("is linear on a long run of incomplete <@ prefixes", () => { + const noisy = `${"<@".repeat(200)} how do I join?`; + expect(detectIntent(noisy)).toBe("join"); + }); +}); diff --git a/apps/dsgt-slack/src/replies.ts b/apps/dsgt-slack/src/replies.ts new file mode 100644 index 00000000..db01ff29 --- /dev/null +++ b/apps/dsgt-slack/src/replies.ts @@ -0,0 +1,125 @@ +/** + * 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 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"; + +/** + * Slack mentions are `<@U…>` (optionally `<@U…|label>`). Walk the string once + * with indexOf so a flood of `<@` without a closing `>` cannot backtrack. + */ +export function stripBotMention(text: string): string { + let output = ""; + let i = 0; + while (i < text.length) { + const start = text.indexOf("<@", i); + if (start === -1) { + output += text.slice(i); + break; + } + output += text.slice(i, start); + const end = text.indexOf(">", start + 2); + if (end === -1) { + output += text.slice(start); + break; + } + output += " "; + i = end + 1; + } + + let collapsed = ""; + for (const ch of output) { + const isSpace = ch === " " || ch === "\t" || ch === "\n" || ch === "\r"; + if (isSpace) { + if (collapsed.length > 0 && collapsed[collapsed.length - 1] !== " ") { + collapsed += " "; + } + } else { + collapsed += ch; + } + } + return collapsed.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/docs/README.md b/docs/README.md index 1544941c..676c5b48 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,12 @@ Start here, then jump to the page that matches the work you are doing. | [Database](./packages/db.md) | `@query/db` | Drizzle schema, client, membership rules | | [UI](./packages/ui.md) | `@query/ui` | Shared React components and styles | +## Apps + +| Document | Workspace | Role | +| --- | --- | --- | +| [dsgt Slack bot](../apps/dsgt-slack/README.md) | `@query/dsgt-slack` | `@dsgt` Slack bot; production HTTP on mainweb | + ## Sites | Document | Workspace | Role | diff --git a/docs/architecture.md b/docs/architecture.md index f91c56f5..92e1fa89 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -15,6 +15,7 @@ │ / /team /events /login /dashboard /admin /judge … │ │ │ │ │ /api/trpc /api/auth /api/webhooks │ +│ stripe + slack │ └──────────┬──────────────────┴───────────────┬───────────────┘ │ │ ┌──────▼──────┐ ┌──────▼──────┐ @@ -91,7 +92,9 @@ Stripe Checkout and Payment Intents both exist. Amounts are defined once in `@qu | Bootcamp add-on (on top of membership) | `1000` ($10) | | Max charge treated as membership | `10000` | -The webhook is `sites/mainweb/app/(portal)/api/webhooks/stripe/route.ts`. Linking can also happen from the portal (`stripe.linkAccount`, `stripe.attemptAutoLink`) and at sign-in. +The Stripe webhook is `sites/mainweb/app/(portal)/api/webhooks/stripe/route.ts`. Linking can also happen from the portal (`stripe.linkAccount`, `stripe.attemptAutoLink`) and at sign-in. + +The Slack Events API webhook is `sites/mainweb/app/(portal)/api/webhooks/slack/route.ts`. It imports reply text from `apps/dsgt-slack` and verifies `SLACK_SIGNING_SECRET`. Production is HTTP on App Hosting (`{AUTH_URL}/api/webhooks/slack`); Socket Mode is local-only. See [`apps/dsgt-slack/README.md`](../apps/dsgt-slack/README.md). ## Caching @@ -106,9 +109,12 @@ The webhook is `sites/mainweb/app/(portal)/api/webhooks/stripe/route.ts`. Linkin The event site does not talk to the database. Interest and registration live on the portal; the marketing site links to `/login?callbackUrl=/hacklytics`. +## Apps + +`apps/dsgt-slack` (`@query/dsgt-slack`) is the `@dsgt` Slack bot. Reply copy lives in that package. Production HTTP is the mainweb route `/api/webhooks/slack` on App Hosting. Socket Mode is a local-only process in the same package. + ## What is not in this repo - `packages/consts` is mentioned in older notes and is **not** a workspace today. -- `apps/*` is listed in `pnpm-workspace.yaml` but there is no `apps/` directory. - `graphify-out/` is generated graph output, not product code. - `trust badge/` holds MLH league badge SVGs for the event site. diff --git a/docs/getting-started.md b/docs/getting-started.md index 07ad9c4b..ee4ea6de 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -22,7 +22,7 @@ corepack prepare pnpm@10.33.2 --activate pnpm install ``` -Workspaces are defined in `pnpm-workspace.yaml`: `sites/*`, `packages/*`, and `tooling/*`. +Workspaces are defined in `pnpm-workspace.yaml`: `apps/*`, `sites/*`, `packages/*`, and `tooling/*`. ## Local database @@ -88,7 +88,7 @@ pnpm --filter @query/db studio # Drizzle Studio | `pnpm build` | `turbo run build` | | `pnpm lint` | ESLint across workspaces (`--max-warnings 0`) | | `pnpm typecheck` | `tsc --noEmit` via Turbo | -| `pnpm test` | Vitest: `packages/api`, `packages/db`, `sites/mainweb/lib` | +| `pnpm test` | Vitest: `packages/api`, `packages/db`, `sites/mainweb/lib`, `apps/dsgt-slack` | | `pnpm format` | Prettier write | Database scripts live on `@query/db`: diff --git a/docs/operations/deployment.md b/docs/operations/deployment.md index 13e7abe6..1c47ba7c 100644 --- a/docs/operations/deployment.md +++ b/docs/operations/deployment.md @@ -28,7 +28,15 @@ Run: `node sites/mainweb/.next/standalone/sites/mainweb/server.js` Secrets are GCP Secret Manager. Grant the App Hosting backend access once per secret, e.g. `firebase apphosting:secrets:grantaccess DATABASE_URL --backend query`. -Env mapping (names only) is in `apphosting.yaml`: `DATABASE_URL`, `AUTH_SECRET` (also copied to `NEXTAUTH_SECRET`), Google/GitHub OAuth, Stripe, SMTP, DDoS ceilings, `TRUSTED_PROXY_HOPS=1`. +Env mapping (names only) is in `apphosting.yaml`: `DATABASE_URL`, `AUTH_SECRET` (also copied to `NEXTAUTH_SECRET`), Google/GitHub OAuth, Stripe, Slack (`SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `SLACK_SOCKET_MODE=false`), SMTP, DDoS ceilings, `TRUSTED_PROXY_HOPS=1`. + +Slack Event Subscriptions, slash commands, and interactivity must use the App Hosting origin (`AUTH_URL` / `NEXTAUTH_URL` in `apphosting.yaml`, host `datasciencegt.org`), not Firebase Hosting `dsgt-website` / `sites/mainweb/out`: + +```text +{AUTH_URL}/api/webhooks/slack +``` + +Create `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` in Secret Manager before that deploy. See [`apps/dsgt-slack/README.md`](../../apps/dsgt-slack/README.md). ## Hacklytics — Firebase Hosting diff --git a/docs/operations/environment.md b/docs/operations/environment.md index 79af41a5..391031c9 100644 --- a/docs/operations/environment.md +++ b/docs/operations/environment.md @@ -64,6 +64,17 @@ Without `DATABASE_URL`, `db` is null, sessions fall back to JWT, and tRPC proced | `DDOS_CLEANUP_INTERVAL_MS` | Code default if unset | | `CSP_ENFORCE` | `true` turns CSP from Report-Only into enforcing. Default is report-only | +## Slack (`@dsgt`) + +| Variable | Notes | +| --- | --- | +| `SLACK_BOT_TOKEN` | Bot User OAuth Token. App Hosting secret `SLACK_BOT_TOKEN` | +| `SLACK_SIGNING_SECRET` | Request signature. App Hosting secret `SLACK_SIGNING_SECRET` | +| `SLACK_SOCKET_MODE` | App Hosting sets `false`. Local Socket Mode defaults to on | +| `SLACK_APP_TOKEN` | Local Socket Mode only (`xapp-...`). Not set in production | + +Create the Secret Manager secrets before the App Hosting deploy that references them. Install steps: [`apps/dsgt-slack/README.md`](../../apps/dsgt-slack/README.md). + ## Runtime (App Hosting) | Variable | Value | diff --git a/docs/operations/testing.md b/docs/operations/testing.md index accbfd5d..3fe04c73 100644 --- a/docs/operations/testing.md +++ b/docs/operations/testing.md @@ -9,7 +9,7 @@ pnpm test That is: ```bash -vitest run packages/api packages/db sites/mainweb/lib +vitest run packages/api packages/db sites/mainweb/lib apps/dsgt-slack ``` CI: `.github/workflows/test.yml` on pushes to `main`/`dev` and on pull requests. @@ -41,6 +41,12 @@ Tests use `src/test/create-mock-context.ts` and `.internal-tests/_db-tx-mock.ts` `src/services/membership.test.ts` — grant, lapse, current-edition resolution, payment linking. +## `@query/dsgt-slack` + +`apps/dsgt-slack/src/replies.test.ts` — join FAQ copy (August 26 / 8/26 / datasciencegt.org) and slash-command routing. + +`apps/dsgt-slack/src/http.test.ts` — Slack signature verification, `url_verification` challenge, `app_mention` join reply, `/dsgt join` ack. + ## Mainweb `sites/mainweb/lib/*.test.ts` — phone formatting, hackathon slugs, `safe-callback`. diff --git a/docs/sites/mainweb.md b/docs/sites/mainweb.md index 55575b3d..de5935de 100644 --- a/docs/sites/mainweb.md +++ b/docs/sites/mainweb.md @@ -65,6 +65,7 @@ Unauthenticated and authenticated product UI. `proxy.ts` marks these prefixes `p | `/api/auth/[...nextauth]` | NextAuth handlers | | `/api/auth/verify-email` | Email-code verification | | `/api/webhooks/stripe` | Stripe webhooks | +| `/api/webhooks/slack` | Slack Events API, `/dsgt`, interactivity (`@query/dsgt-slack`) | ## Client data @@ -77,7 +78,7 @@ Helpers: `lib/hackathon-slug.ts`, `lib/phone.ts`, `lib/bootcamp-schedule.ts`, `l ## Config highlights (`next.config.mjs`) -- Transpiles `@query/api`, `@query/auth`, `@query/db`, `@query/ui` +- Transpiles `@query/api`, `@query/auth`, `@query/db`, `@query/dsgt-slack`, `@query/ui` - `outputFileTracingRoot` is the monorepo root (needed for standalone on App Hosting) - Security headers: HSTS, CSP (report-only unless `CSP_ENFORCE=true`), frame options SAMEORIGIN (admin print/QR iframes), Permissions-Policy (camera **not** denied — `/scan` needs it) - React Compiler enabled diff --git a/package.json b/package.json index 0954cf6d..7bed64cb 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", @@ -20,7 +20,7 @@ }, "devDependencies": { "turbo": "^2.9.14", - "vitest": "^4.1.8" + "vitest": "^4.1.10" }, "pnpm": { "overrides": { diff --git a/packages/api/package.json b/packages/api/package.json index d849b5ce..4c92f752 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -42,6 +42,6 @@ "@query/tsconfig": "workspace:*", "@types/sanitize-html": "^2.16.0", "typescript": "6.0.2", - "vitest": "^4.1.8" + "vitest": "^4.1.10" } } diff --git a/packages/auth/package.json b/packages/auth/package.json index 70ae81e2..22f3b5a8 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -13,7 +13,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@auth/drizzle-adapter": "1.7.1", + "@auth/drizzle-adapter": "1.11.3", "@query/db": "workspace:*", "drizzle-orm": "0.45.2", "next-auth": "5.0.0-beta.32", @@ -25,9 +25,9 @@ "devDependencies": { "@query/tsconfig": "workspace:*", "@types/node": "^20", - "@typescript-eslint/eslint-plugin": "^8.59.2", - "@typescript-eslint/parser": "^8.59.2", - "@typescript-eslint/utils": "^8.59.2", + "@typescript-eslint/eslint-plugin": "^8.67.0", + "@typescript-eslint/parser": "^8.67.0", + "@typescript-eslint/utils": "^8.67.0", "eslint": "^10.1.0", "eslint-config-prettier": "^10.1.5", "typescript": "^6.0.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24c9ca8b..80e5d382 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: dependencies: next: specifier: 16.3.0 - version: 16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) + version: 16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) typescript: specifier: ^6.0.2 version: 6.0.2 @@ -34,8 +34,51 @@ importers: specifier: ^2.9.14 version: 2.9.14 vitest: - specifier: ^4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0) + specifier: ^4.1.10 + version: 4.1.11(@opentelemetry/api@1.9.1)(@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) + '@slack/web-api': + specifier: ^7.13.0 + version: 7.19.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.10 + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0) packages/api: dependencies: @@ -53,7 +96,7 @@ importers: version: 11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2) '@trpc/next': specifier: 11.18.0 - version: 11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/react-query@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react-dom@19.0.0(react@19.2.7))(react@19.2.7)(typescript@6.0.2) + version: 11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/react-query@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react-dom@19.0.0(react@19.2.7))(react@19.2.7)(typescript@6.0.2) '@trpc/react-query': specifier: 11.18.0 version: 11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2) @@ -92,14 +135,14 @@ importers: specifier: 6.0.2 version: 6.0.2 vitest: - specifier: ^4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0) + specifier: ^4.1.10 + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0) packages/auth: dependencies: '@auth/drizzle-adapter': - specifier: 1.7.1 - version: 1.7.1(nodemailer@9.0.3) + specifier: 1.11.3 + version: 1.11.3(nodemailer@9.0.3) '@query/db': specifier: workspace:* version: link:../db @@ -108,7 +151,7 @@ importers: version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.3) next-auth: specifier: 5.0.0-beta.32 - version: 5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@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) + version: 5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@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) nodemailer: specifier: ^9.0.1 version: 9.0.3 @@ -120,14 +163,14 @@ importers: specifier: ^20 version: 20.19.40 '@typescript-eslint/eslint-plugin': - specifier: ^8.59.2 - version: 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + specifier: ^8.67.0 + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) '@typescript-eslint/parser': - specifier: ^8.59.2 - version: 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + specifier: ^8.67.0 + version: 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) '@typescript-eslint/utils': - specifier: ^8.59.2 - version: 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + specifier: ^8.67.0 + version: 8.68.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) eslint: specifier: ^10.1.0 version: 10.1.0(jiti@2.7.0) @@ -148,7 +191,7 @@ importers: version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.3) next-auth: specifier: 5.0.0-beta.32 - version: 5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: 5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react@19.2.7) pg: specifier: 8.21.0 version: 8.21.0 @@ -228,7 +271,7 @@ importers: version: 1.0.0 next: specifier: 16.3.0 - version: 16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) + version: 16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) react: specifier: 19.2.7 version: 19.2.7 @@ -239,9 +282,6 @@ importers: '@next/eslint-plugin-next': specifier: 16.3.0 version: 16.3.0(eslint@10.1.0(jiti@2.7.0)) - '@playwright/test': - specifier: ^1.60.0 - version: 1.60.0 '@tailwindcss/postcss': specifier: 4.3.0 version: 4.3.0 @@ -273,8 +313,8 @@ importers: specifier: 5.8.3 version: 5.8.3 typescript-eslint: - specifier: ^8.59.2 - version: 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + specifier: ^8.67.0 + version: 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) sites/mainweb: dependencies: @@ -290,6 +330,9 @@ importers: '@query/db': specifier: workspace:* version: link:../../packages/db + '@query/dsgt-slack': + specifier: workspace:* + version: link:../../apps/dsgt-slack '@query/ui': specifier: workspace:* version: link:../../packages/ui @@ -307,7 +350,7 @@ importers: version: 11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2) '@trpc/next': specifier: 11.18.0 - version: 11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/react-query@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react-dom@19.0.0(react@19.2.7))(react@19.2.7)(typescript@6.0.2) + version: 11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/react-query@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react-dom@19.0.0(react@19.2.7))(react@19.2.7)(typescript@6.0.2) '@trpc/react-query': specifier: 11.18.0 version: 11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2) @@ -325,19 +368,19 @@ importers: version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.3) geist: specifier: ^1.5.1 - version: 1.7.0(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7)) + version: 1.7.0(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7)) lucide-react: - specifier: ^1.14.0 - version: 1.14.0(react@19.2.7) + specifier: ^1.31.0 + version: 1.35.0(react@19.2.7) minimatch: specifier: 10.2.3 version: 10.2.3 next: specifier: 16.3.0 - version: 16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) + version: 16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) next-auth: specifier: 5.0.0-beta.32 - version: 5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: 5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react@19.2.7) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.0.0(react@19.2.7))(react@19.2.7) @@ -433,8 +476,8 @@ importers: specifier: ^6.0.2 version: 6.0.2 typescript-eslint: - specifier: ^8.59.2 - version: 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + specifier: ^8.67.0 + version: 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) tooling/eslint: dependencies: @@ -442,23 +485,23 @@ importers: specifier: 1.2.0 version: 1.2.0(eslint@10.1.0(jiti@2.7.0)) '@eslint/js': - specifier: 9.17.0 - version: 9.17.0 + specifier: 10.0.1 + version: 10.0.1(eslint@10.1.0(jiti@2.7.0)) '@next/eslint-plugin-next': specifier: 16.3.0 version: 16.3.0(eslint@10.1.0(jiti@2.7.0)) '@typescript-eslint/eslint-plugin': - specifier: 8.59.2 - version: 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + specifier: 8.67.0 + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) '@typescript-eslint/parser': - specifier: 8.59.2 - version: 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + specifier: 8.67.0 + version: 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) eslint-config-prettier: specifier: 9.1.0 version: 9.1.0(eslint@10.1.0(jiti@2.7.0)) eslint-plugin-import: specifier: 2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0)) + version: 2.31.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0)) eslint-plugin-jsx-a11y: specifier: 6.10.2 version: 6.10.2(eslint@10.1.0(jiti@2.7.0)) @@ -478,8 +521,8 @@ importers: specifier: 10.2.3 version: 10.2.3 typescript-eslint: - specifier: 8.59.2 - version: 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + specifier: 8.67.0 + version: 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) devDependencies: '@query/prettier-config': specifier: workspace:* @@ -529,7 +572,7 @@ importers: version: 4.0.0 next: specifier: 16.3.0 - version: 16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) + version: 16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) postcss: specifier: ^8.5.23 version: 8.5.23 @@ -576,8 +619,8 @@ packages: nodemailer: optional: true - '@auth/drizzle-adapter@1.7.1': - resolution: {integrity: sha512-WJSh39zEtynJw6GVS8MCnB2qXw1q7IkMbG2GiHx4jiQWDpHXE+idGSeBuZXS+J8tQnLexQa6v8hnz+wmalM74w==} + '@auth/drizzle-adapter@1.11.3': + resolution: {integrity: sha512-TxqVasPVuf7LDAT1Yuu6bftpuet8o0tjdXW+mXpnXWdSRPQsomdzMoz9RsXkFN/JfdK+/DwdgOEmOTv1gbiFNw==} '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} @@ -828,9 +871,14 @@ packages: resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.17.0': - resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true '@eslint/object-schema@3.0.3': resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} @@ -1248,11 +1296,6 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} - '@playwright/test@1.60.0': - resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} - engines: {node: '>=18'} - hasBin: true - '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -1779,6 +1822,46 @@ 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@4.0.1': + resolution: {integrity: sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==} + engines: {node: '>= 18', npm: '>= 8.6.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.1': + resolution: {integrity: sha512-7L4adgnJtQvQgo5cfIwqHG1QuR8mdZQ/WdMScNDp6uK644ZqnHDAa4rR+vZYfZFdJ5+obCqZQ66Dad8kcwJ4Jg==} + engines: {node: '>=20', npm: '>=9.6.4'} + peerDependencies: + undici: ^6.27.0 + + '@slack/types@2.22.0': + resolution: {integrity: sha512-sZ9lIgJhPX2qft/tKWiklFlc0o1FWeI7QtciZJfW1+ErH1eGGHvOZ8e73sleTCFEFJp1q/R0WeS8Oa7AsiDprg==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + + '@slack/types@3.1.0': + resolution: {integrity: sha512-bTzqrO3lxJ5iWedo1eJKNAs1koyEhaTwKLJUkD3KETnPeQvD978AAE4jvWstUws8Gbbc4JR6iYE6F7XjE8UGmw==} + engines: {node: '>= 20', npm: '>=9.6.4'} + + '@slack/web-api@7.19.0': + resolution: {integrity: sha512-ItjyjEZml+LDH8CjcCLRLJHh7VZtevPKExrRN3l5KWyBliyDnGAeoO4Y+K+fFBmRpKLYVPgqWMX4THldv2HVtA==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + + '@slack/web-api@8.1.1': + resolution: {integrity: sha512-am/z/LLbEC7gNQp6FpaGT0l4XnWqcXetpwdgZHzS7Nbb96q9R9QwWA3lo0ZXGuAHDkct0MvFnouzuc2Y4XNmow==} + engines: {node: '>= 20', npm: '>=9.6.4'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -2010,9 +2093,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==} @@ -2034,16 +2123,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==} @@ -2060,6 +2164,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: @@ -2071,9 +2181,21 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@types/retry@0.12.5': + resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==} + '@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==} @@ -2083,70 +2205,107 @@ packages: '@types/webxr@0.5.24': resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} - '@typescript-eslint/eslint-plugin@8.59.2': - resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} + '@typescript-eslint/eslint-plugin@8.67.0': + resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.2 + '@typescript-eslint/parser': ^8.67.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.2': - resolution: {integrity: sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==} + '@typescript-eslint/parser@8.67.0': + resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.59.2': - resolution: {integrity: sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==} + '@typescript-eslint/project-service@8.67.0': + resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.2': - resolution: {integrity: sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==} + '@typescript-eslint/project-service@8.68.0': + resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.67.0': + resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/scope-manager@8.68.0': + resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.67.0': + resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/tsconfig-utils@8.59.2': - resolution: {integrity: sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==} + '@typescript-eslint/tsconfig-utils@8.68.0': + resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.2': - resolution: {integrity: sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==} + '@typescript-eslint/type-utils@8.67.0': + resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.59.2': - resolution: {integrity: sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==} + '@typescript-eslint/types@8.67.0': + resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/types@8.68.0': + resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.67.0': + resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/typescript-estree@8.68.0': + resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@8.59.2': - resolution: {integrity: sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==} + '@typescript-eslint/utils@8.67.0': + resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.2': - resolution: {integrity: sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==} + '@typescript-eslint/utils@8.68.0': + resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.59.2': - resolution: {integrity: sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==} + '@typescript-eslint/visitor-keys@8.67.0': + resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/expect@4.1.8': - resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} + '@typescript-eslint/visitor-keys@8.68.0': + resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} - '@vitest/mocker@4.1.8': - resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: msw: ^2.4.9 vite: ^7.3.5 @@ -2156,20 +2315,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.8': - resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - '@vitest/runner@4.1.8': - resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - '@vitest/snapshot@4.1.8': - resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - '@vitest/spy@4.1.8': - resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - '@vitest/utils@4.1.8': - resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} '@yudiel/react-qr-scanner@2.5.1': resolution: {integrity: sha512-FWzHaCneu30mQpE80VNWx4IPtBjXFEiTzhwWunZy3afvvAy/x0aVIgYijJKEbROoaAeDfcJ/gIyUCqPBP7bMOw==} @@ -2177,6 +2336,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: @@ -2191,6 +2354,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} @@ -2253,6 +2420,9 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.22: resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} engines: {node: ^10 || ^12 || >=14} @@ -2268,6 +2438,9 @@ packages: resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} engines: {node: '>=4'} + axios@1.20.0: + resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -2294,6 +2467,10 @@ packages: bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + brace-expansion@5.0.9: resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} @@ -2307,9 +2484,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'} @@ -2357,13 +2541,37 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@7.2.0: 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'} @@ -2437,6 +2645,14 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + 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'} @@ -2575,6 +2791,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==} @@ -2584,6 +2806,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'} @@ -2640,6 +2866,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'} @@ -2759,10 +2988,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==} @@ -2799,6 +3042,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'} @@ -2814,10 +3061,27 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -2835,10 +3099,9 @@ packages: react-dom: optional: true - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -2937,12 +3200,28 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + 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'} @@ -2960,10 +3239,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'} @@ -2996,6 +3282,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-electron@2.2.2: + resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3036,6 +3325,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'} @@ -3048,6 +3340,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -3114,10 +3410,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==} @@ -3217,6 +3523,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==} @@ -3224,8 +3551,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lucide-react@1.14.0: - resolution: {integrity: sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==} + lucide-react@1.35.0: + resolution: {integrity: sha512-yXCCWxGFYT6bLIPYC4SY6fPQPRs/d797rRIue+J9XP2Td6vQvD53gaQRBCnIVT1kTQRHtAtxlfOQNWAuIF8ELg==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3236,6 +3563,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'} @@ -3247,6 +3582,22 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + 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} @@ -3286,6 +3637,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + next-auth@5.0.0-beta.32: resolution: {integrity: sha512-CGlChIEWZ6LltNVxrE5yiySMID+Idpmry47JYA5lLwgD8Sx02a8M65VL0TWVz9nbnOioS/tCW/rP/0+mE7Qp4Q==} peerDependencies: @@ -3385,6 +3740,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 @@ -3397,6 +3759,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'} @@ -3413,6 +3779,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'} @@ -3420,6 +3798,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'} @@ -3431,6 +3813,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==} @@ -3482,16 +3867,6 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - playwright-core@1.60.0: - resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.60.0: - resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} - engines: {node: '>=18'} - hasBin: true - pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -3617,6 +3992,14 @@ 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'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3631,9 +4014,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: @@ -3711,6 +4106,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'} @@ -3720,6 +4119,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==} @@ -3727,6 +4130,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'} @@ -3735,6 +4141,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==} @@ -3761,6 +4170,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==} @@ -3776,6 +4193,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'} @@ -3809,6 +4229,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==} @@ -3834,6 +4258,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==} @@ -3945,6 +4373,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'} @@ -3961,6 +4393,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'} @@ -3978,6 +4414,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'} @@ -3994,8 +4434,8 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.59.2: - resolution: {integrity: sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==} + typescript-eslint@8.67.0: + resolution: {integrity: sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4018,6 +4458,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 @@ -4047,6 +4495,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} @@ -4087,20 +4539,20 @@ packages: yaml: optional: true - vitest@4.1.8: - resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.8 - '@vitest/browser-preview': 4.1.8 - '@vitest/browser-webdriverio': 4.1.8 - '@vitest/coverage-istanbul': 4.1.8 - '@vitest/coverage-v8': 4.1.8 - '@vitest/ui': 4.1.8 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4173,6 +4625,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'} @@ -4230,7 +4685,7 @@ snapshots: optionalDependencies: nodemailer: 9.0.3 - '@auth/drizzle-adapter@1.7.1(nodemailer@9.0.3)': + '@auth/drizzle-adapter@1.11.3(nodemailer@9.0.3)': dependencies: '@auth/core': 0.41.3(nodemailer@9.0.3) transitivePeerDependencies: @@ -4415,7 +4870,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@9.17.0': {} + '@eslint/js@10.0.1(eslint@10.1.0(jiti@2.7.0))': + optionalDependencies: + eslint: 10.1.0(jiti@2.7.0) '@eslint/object-schema@3.0.3': {} @@ -4733,10 +5190,6 @@ snapshots: '@parcel/watcher-win32-ia32': 2.5.6 '@parcel/watcher-win32-x64': 2.5.6 - '@playwright/test@1.60.0': - dependencies: - playwright: 1.60.0 - '@polka/url@1.0.0-next.29': {} '@radix-ui/number@1.1.1': {} @@ -5214,6 +5667,79 @@ 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.1(undici@6.28.0) + '@slack/types': 3.1.0 + '@slack/web-api': 8.1.1 + '@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@4.0.1': + dependencies: + '@types/node': 22.15.32 + + '@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.1.1 + '@types/jsonwebtoken': 9.0.10 + '@types/node': 22.15.32 + jsonwebtoken: 9.0.3 + + '@slack/socket-mode@3.0.1(undici@6.28.0)': + dependencies: + '@slack/logger': 5.0.0 + '@slack/web-api': 8.1.1 + '@types/node': 22.15.32 + eventemitter3: 5.0.4 + undici: 6.28.0 + + '@slack/types@2.22.0': {} + + '@slack/types@3.1.0': {} + + '@slack/web-api@7.19.0': + dependencies: + '@slack/logger': 4.0.1 + '@slack/types': 2.22.0 + '@types/node': 22.15.32 + '@types/retry': 0.12.0 + axios: 1.20.0 + eventemitter3: 5.0.4 + form-data: 4.0.6 + is-electron: 2.2.2 + is-stream: 2.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + retry: 0.13.1 + transitivePeerDependencies: + - debug + - supports-color + + '@slack/web-api@8.1.1': + dependencies: + '@slack/logger': 5.0.0 + '@slack/types': 3.1.0 + '@types/node': 22.15.32 + '@types/retry': 0.12.5 + 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)': @@ -5341,11 +5867,11 @@ snapshots: '@trpc/server': 11.18.0(typescript@6.0.2) typescript: 6.0.2 - '@trpc/next@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/react-query@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react-dom@19.0.0(react@19.2.7))(react@19.2.7)(typescript@6.0.2)': + '@trpc/next@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/react-query@11.18.0(@tanstack/react-query@5.90.12(react@19.2.7))(@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(react@19.2.7)(typescript@6.0.2))(@trpc/server@11.18.0(typescript@6.0.2))(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react-dom@19.0.0(react@19.2.7))(react@19.2.7)(typescript@6.0.2)': dependencies: '@trpc/client': 11.18.0(@trpc/server@11.18.0(typescript@6.0.2))(typescript@6.0.2) '@trpc/server': 11.18.0(typescript@6.0.2) - next: 16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) + next: 16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.0.0(react@19.2.7) typescript: 6.0.2 @@ -5385,11 +5911,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': {} @@ -5409,14 +5944,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 @@ -5441,6 +5998,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 @@ -5453,10 +6014,23 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/retry@0.12.0': {} + + '@types/retry@0.12.5': {} + '@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': @@ -5470,14 +6044,14 @@ snapshots: '@types/webxr@0.5.24': {} - '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3))(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3))(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/type-utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) - '@typescript-eslint/utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/parser': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/type-utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.67.0 eslint: 10.1.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 @@ -5486,14 +6060,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/type-utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) - '@typescript-eslint/utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/parser': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/type-utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.67.0 eslint: 10.1.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 @@ -5502,66 +6076,84 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': + '@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 eslint: 10.1.0(jiti@2.7.0) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': dependencies: - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 eslint: 10.1.0(jiti@2.7.0) typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.2(typescript@5.8.3)': + '@typescript-eslint/project-service@8.67.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.8.3) - '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.8.3) + '@typescript-eslint/types': 8.67.0 debug: 4.4.3 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.2(typescript@6.0.2)': + '@typescript-eslint/project-service@8.67.0(typescript@6.0.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.2) + '@typescript-eslint/types': 8.67.0 + debug: 4.4.3 + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.68.0(typescript@6.0.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.2) - '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.2) + '@typescript-eslint/types': 8.68.0 debug: 4.4.3 typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.2': + '@typescript-eslint/scope-manager@8.67.0': + dependencies: + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 + + '@typescript-eslint/scope-manager@8.68.0': dependencies: - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 - '@typescript-eslint/tsconfig-utils@8.59.2(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/tsconfig-utils@8.59.2(typescript@6.0.2)': + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.2)': + dependencies: + typescript: 6.0.2 + + '@typescript-eslint/tsconfig-utils@8.68.0(typescript@6.0.2)': dependencies: typescript: 6.0.2 - '@typescript-eslint/type-utils@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.8.3) - '@typescript-eslint/utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) debug: 4.4.3 eslint: 10.1.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.8.3) @@ -5569,11 +6161,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': dependencies: - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.2) - '@typescript-eslint/utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.2) + '@typescript-eslint/utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) debug: 4.4.3 eslint: 10.1.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.2) @@ -5581,16 +6173,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.2': {} + '@typescript-eslint/types@8.67.0': {} - '@typescript-eslint/typescript-estree@8.59.2(typescript@5.8.3)': + '@typescript-eslint/types@8.68.0': {} + + '@typescript-eslint/typescript-estree@8.67.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/project-service': 8.59.2(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.8.3) - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/project-service': 8.67.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.8.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 - minimatch: 10.2.4 + minimatch: 10.2.3 semver: 7.8.5 tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@5.8.3) @@ -5598,14 +6192,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.59.2(typescript@6.0.2)': + '@typescript-eslint/typescript-estree@8.67.0(typescript@6.0.2)': dependencies: - '@typescript-eslint/project-service': 8.59.2(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.2) - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/project-service': 8.67.0(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.2) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 - minimatch: 10.2.4 + minimatch: 10.2.3 semver: 7.8.5 tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@6.0.2) @@ -5613,71 +6207,102 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.68.0(typescript@6.0.2)': + dependencies: + '@typescript-eslint/project-service': 8.68.0(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.2) + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 + debug: 4.4.3 + minimatch: 10.2.3 + semver: 7.8.5 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.8.3) eslint: 10.1.0(jiti@2.7.0) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/utils@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.2) + eslint: 10.1.0(jiti@2.7.0) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.68.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.68.0 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.2) eslint: 10.1.0(jiti@2.7.0) typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.2': + '@typescript-eslint/visitor-keys@8.67.0': + dependencies: + '@typescript-eslint/types': 8.67.0 + eslint-visitor-keys: 5.0.1 + + '@typescript-eslint/visitor-keys@8.68.0': dependencies: - '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/types': 8.68.0 eslint-visitor-keys: 5.0.1 - '@vitest/expect@4.1.8': + '@vitest/expect@4.1.11': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.8 - '@vitest/utils': 4.1.8 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(vite@7.3.6(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0))': + '@vitest/mocker@4.1.11(vite@7.3.6(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0))': dependencies: - '@vitest/spy': 4.1.8 + '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.3.6(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0) - '@vitest/pretty-format@4.1.8': + '@vitest/pretty-format@4.1.11': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.8': + '@vitest/runner@4.1.11': dependencies: - '@vitest/utils': 4.1.8 + '@vitest/utils': 4.1.11 pathe: 2.0.3 - '@vitest/snapshot@4.1.8': + '@vitest/snapshot@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.8 - '@vitest/utils': 4.1.8 + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.8': {} + '@vitest/spy@4.1.11': {} - '@vitest/utils@4.1.8': + '@vitest/utils@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.8 + '@vitest/pretty-format': 4.1.11 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -5688,6 +6313,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.1.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -5698,6 +6328,12 @@ snapshots: acorn@8.16.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -5790,6 +6426,8 @@ snapshots: async-function@1.0.0: {} + asynckit@0.4.0: {} + autoprefixer@10.4.22(postcss@8.5.23): dependencies: browserslist: 4.28.2 @@ -5806,6 +6444,16 @@ snapshots: axe-core@4.11.4: {} + axios@1.20.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + axobject-query@4.1.0: {} babel-plugin-react-compiler@1.0.0: @@ -5824,6 +6472,20 @@ snapshots: bintrees@1.0.2: {} + 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.9: dependencies: balanced-match: 4.0.4 @@ -5840,8 +6502,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 @@ -5889,10 +6555,24 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + 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 @@ -5959,6 +6639,10 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -6013,12 +6697,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 @@ -6162,6 +6854,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)): @@ -6180,17 +6874,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.1.0(jiti@2.7.0)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.1.0(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/parser': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) eslint: 10.1.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -6201,7 +6895,7 @@ snapshots: doctrine: 2.1.0 eslint: 10.1.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.1.0(jiti@2.7.0)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint-import-resolver-node@0.3.10)(eslint@10.1.0(jiti@2.7.0)) hasown: 2.0.3 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -6213,7 +6907,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/parser': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -6340,8 +7034,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: @@ -6374,6 +7107,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 @@ -6391,10 +7135,22 @@ snapshots: flatted@3.4.2: {} + follow-redirects@1.16.0: {} + for-each@0.3.5: dependencies: is-callable: 1.2.7 + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + 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): @@ -6406,8 +7162,7 @@ snapshots: react: 19.2.7 react-dom: 19.0.0(react@19.2.7) - fsevents@2.3.2: - optional: true + fresh@2.0.0: {} fsevents@2.3.3: optional: true @@ -6425,9 +7180,9 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.7.0(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7)): + geist@1.7.0(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7)): dependencies: - next: 16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) + next: 16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) generator-function@2.0.1: {} @@ -6506,6 +7261,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + html-escaper@2.0.2: {} htmlparser2@10.1.0: @@ -6515,6 +7274,25 @@ 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 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -6523,12 +7301,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 @@ -6569,6 +7351,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-electron@2.2.2: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -6602,6 +7386,8 @@ snapshots: is-plain-object@5.0.0: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -6615,6 +7401,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@2.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -6674,6 +7462,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 @@ -6681,6 +7482,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 @@ -6757,13 +7569,27 @@ 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: dependencies: js-tokens: 4.0.0 - lucide-react@1.14.0(react@19.2.7): + lucide-react@1.35.0(react@19.2.7): dependencies: react: 19.2.7 @@ -6773,6 +7599,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: {} @@ -6782,6 +7612,18 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + minimatch@10.2.3: dependencies: brace-expansion: 5.0.9 @@ -6812,18 +7654,22 @@ snapshots: natural-compare@1.4.0: {} - next-auth@5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@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): + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + next-auth@5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@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) - next: 16.3.0(@opentelemetry/api@1.9.1)(@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) + next: 16.3.0(@opentelemetry/api@1.9.1)(@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) react: 19.2.7 optionalDependencies: nodemailer: 9.0.3 - next-auth@5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react@19.2.7): + next-auth@5.0.0-beta.32(next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7))(react@19.2.7): dependencies: '@auth/core': 0.41.3(nodemailer@9.0.3) - next: 16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) + next: 16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7) react: 19.2.7 next-themes@0.4.6(react-dom@19.0.0(react@19.2.7))(react@19.2.7): @@ -6831,7 +7677,7 @@ snapshots: react: 19.2.7 react-dom: 19.0.0(react@19.2.7) - next@16.3.0(@opentelemetry/api@1.9.1)(@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): + next@16.3.0(@opentelemetry/api@1.9.1)(@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): dependencies: '@next/env': 16.3.0 '@swc/helpers': 0.5.15 @@ -6851,7 +7697,6 @@ snapshots: '@next/swc-win32-arm64-msvc': 16.3.0 '@next/swc-win32-x64-msvc': 16.3.0 '@opentelemetry/api': 1.9.1 - '@playwright/test': 1.60.0 babel-plugin-react-compiler: 1.0.0 sharp: 0.35.3(@types/node@20.19.40) transitivePeerDependencies: @@ -6859,7 +7704,7 @@ snapshots: - '@types/node' - babel-plugin-macros - next@16.3.0(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7): + next@16.3.0(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(babel-plugin-react-compiler@1.0.0)(react-dom@19.0.0(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.3.0 '@swc/helpers': 0.5.15 @@ -6879,7 +7724,6 @@ snapshots: '@next/swc-win32-arm64-msvc': 16.3.0 '@next/swc-win32-x64-msvc': 16.3.0 '@opentelemetry/api': 1.9.1 - '@playwright/test': 1.60.0 babel-plugin-react-compiler: 1.0.0 sharp: 0.35.3(@types/node@22.15.32) transitivePeerDependencies: @@ -6948,6 +7792,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: @@ -6965,6 +7817,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 @@ -6981,16 +7835,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: @@ -7036,14 +7908,6 @@ snapshots: picomatch@4.0.4: {} - playwright-core@1.60.0: {} - - playwright@1.60.0: - dependencies: - playwright-core: 1.60.0 - optionalDependencies: - fsevents: 2.3.2 - pngjs@5.0.0: {} possible-typed-array-names@1.1.0: {} @@ -7097,6 +7961,13 @@ 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 + + proxy-from-env@2.1.0: {} + punycode@2.3.1: {} qrcode.react@3.2.0(react@19.2.7): @@ -7109,8 +7980,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 @@ -7194,6 +8079,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: @@ -7227,6 +8114,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 @@ -7239,6 +8136,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 @@ -7250,6 +8149,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 @@ -7280,6 +8181,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: @@ -7304,6 +8230,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 @@ -7406,6 +8334,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: @@ -7427,6 +8363,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@4.0.0: {} stop-iteration-iterator@1.1.0: @@ -7540,6 +8478,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): @@ -7559,6 +8499,8 @@ snapshots: tslib@2.8.1: {} + tsscmp@1.0.6: {} + tsx@4.21.0: dependencies: esbuild: 0.25.12 @@ -7583,6 +8525,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 @@ -7616,23 +8564,23 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3): + typescript-eslint@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3))(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) - '@typescript-eslint/parser': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) - '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.8.3) - '@typescript-eslint/utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3))(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@5.8.3) eslint: 10.1.0(jiti@2.7.0) typescript: 5.8.3 transitivePeerDependencies: - supports-color - typescript-eslint@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2): + typescript-eslint@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) - '@typescript-eslint/parser': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) - '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.2) - '@typescript-eslint/utils': 8.59.2(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/parser': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.2) + '@typescript-eslint/utils': 8.67.0(eslint@10.1.0(jiti@2.7.0))(typescript@6.0.2) eslint: 10.1.0(jiti@2.7.0) typescript: 6.0.2 transitivePeerDependencies: @@ -7651,6 +8599,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 @@ -7676,6 +8628,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 @@ -7691,15 +8645,15 @@ snapshots: lightningcss: 1.32.0 tsx: 4.21.0 - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0): + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: - '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@7.3.6(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)) - '@vitest/pretty-format': 4.1.8 - '@vitest/runner': 4.1.8 - '@vitest/snapshot': 4.1.8 - '@vitest/spy': 4.1.8 - '@vitest/utils': 4.1.8 + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@7.3.6(@types/node@22.15.32)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -7812,6 +8766,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/sites/hacklytics2027/app/globals.css b/sites/hacklytics2027/app/globals.css index 4753052c..2cbdc98a 100644 --- a/sites/hacklytics2027/app/globals.css +++ b/sites/hacklytics2027/app/globals.css @@ -190,20 +190,32 @@ body { .pixel-lime:hover { --pixel-edge: rgba(200, 255, 0, 0.9); --pixel-glow: rgba(200, 255, 0, 0.4); } .pixel-purple:hover { --pixel-edge: rgba(155, 0, 255, 0.95); --pixel-glow: rgba(155, 0, 255, 0.45); } -/* Solid neon slab button — chunky, no radius, steps() so it feels 8-bit */ +/* Solid cyan slab — sharp rectangle, hard pixel edge, no bloom. + Glow stays on the brightest sprites, not on every CTA. */ .pixel-btn { position: relative; + border: 0; border-radius: 0; background: var(--btn-bg, #00e5ff); color: #04040a; + font-weight: 700; + text-decoration: none; box-shadow: - 0 -4px 0 0 rgba(255, 255, 255, 0.35), - 0 4px 0 0 rgba(0, 0, 0, 0.6), - 0 0 28px var(--btn-glow, rgba(0, 229, 255, 0.5)); + 0 -3px 0 0 rgba(255, 255, 255, 0.28), + 0 3px 0 0 rgba(0, 0, 0, 0.75); transition: transform 120ms steps(2), box-shadow 160ms steps(2), filter 160ms linear; } -.pixel-btn:hover { transform: translateY(-2px); filter: brightness(1.12); } -.pixel-btn:active { transform: translateY(2px); } +.pixel-btn:hover { transform: translateY(-2px); filter: brightness(1.08); } +.pixel-btn:active { + transform: translateY(2px); + box-shadow: + 0 -1px 0 0 rgba(255, 255, 255, 0.2), + 0 1px 0 0 rgba(0, 0, 0, 0.8); +} +.pixel-btn:focus-visible { + outline: 2px solid #f0f0f2; + outline-offset: 3px; +} /* Neon text glow, tuned per accent */ .neon-cyan { color: #00e5ff; text-shadow: 0 0 8px rgba(0, 229, 255, 0.65), 0 0 22px rgba(0, 229, 255, 0.35); } diff --git a/sites/hacklytics2027/app/page.tsx b/sites/hacklytics2027/app/page.tsx index 6aca16b3..f135b41f 100644 --- a/sites/hacklytics2027/app/page.tsx +++ b/sites/hacklytics2027/app/page.tsx @@ -4,7 +4,7 @@ import HomeSections from "@/components/HomeSections"; import PixelGarden, { PixelGround } from "@/components/pixel/PixelGarden"; import PixelSprite from "@/components/pixel/PixelSprite"; import { BLOOM, DAISY, SPROUT, TULIP } from "@/components/pixel/sprites"; -import { INTEREST_URL } from "@/lib/links"; +import { INTEREST_HINT, INTEREST_URL } from "@/lib/links"; // ─── Elegant Floral Background ───────────────────────────────────────────── const FloralBackground = () => ( @@ -171,21 +171,25 @@ export default function HomePage() { Join 1,000+ hackers in Atlanta, GA.

- {/* Framer-style CTA Buttons */} + {/* Notify me hands off to the portal. No on-site form. */}
- - - NOTIFY ME - - - + + + NOTIFY ME → + + + {INTEREST_HINT} - + -
- - {/* pixel flower bed growing along the top edge of the footer */} - -
-
+ ); } diff --git a/sites/hacklytics2027/components/HomeSections.tsx b/sites/hacklytics2027/components/HomeSections.tsx index 1b6b6b5c..21480a13 100644 --- a/sites/hacklytics2027/components/HomeSections.tsx +++ b/sites/hacklytics2027/components/HomeSections.tsx @@ -8,56 +8,16 @@ import ScheduleSection from "./sections/Schedule/Schedule"; import FAQSection from "./sections/FAQSection"; import PrizeAndSpeakerSection from "./sections/PrizeAndSpeakerSection"; import SponsorsSection from "./sections/Sponsor"; -import LazySection from "./LazySection"; -import { PixelFloraRow } from "./pixel/PixelBits"; - -// Pixel flower bed between sections — replaces the old hairline divider -function SectionDivider({ seed }: { seed: number }) { - return ( -