From 9c4b0e54bc83e5740bf29f27a36bd2417956b8ea Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:05:49 -0400 Subject: [PATCH 1/2] Remove webhook functions migrated from webhooks repo Removes the Slack/Zoom webhook handlers, scheduled event reminders, and the build-rooms prebuild step brought in by #1501, along with their config, env vars, docs, and now-unused dependencies. The join-coffee and join-slack redirect functions stay. --- .env.example | 18 +- README.md | 20 +- data/.gitignore | 2 - netlify.toml | 18 - netlify/env.d.ts | 17 - netlify/functions/_shared/slack.ts | 18 - netlify/functions/_shared/types/cms.ts | 22 - netlify/functions/_shared/types/room.ts | 15 - netlify/functions/_shared/verify.ts | 77 --- .../functions/event-reminders-daily/index.ts | 162 ------ .../functions/event-reminders-hourly/index.ts | 287 ----------- .../functions/event-reminders-weekly/index.ts | 153 ------ netlify/functions/slack/index.ts | 132 ----- netlify/functions/slack/messages.ts | 161 ------ .../zoom-meeting-webhook-handler/airtable.ts | 42 -- .../zoom-meeting-webhook-handler/index.ts | 165 ------ .../zoom-meeting-webhook-handler/slack.ts | 116 ----- package.json | 9 +- pnpm-lock.yaml | 487 +----------------- scripts/build-rooms.ts | 33 -- 20 files changed, 9 insertions(+), 1945 deletions(-) delete mode 100644 data/.gitignore delete mode 100644 netlify/functions/_shared/slack.ts delete mode 100644 netlify/functions/_shared/types/cms.ts delete mode 100644 netlify/functions/_shared/types/room.ts delete mode 100644 netlify/functions/_shared/verify.ts delete mode 100644 netlify/functions/event-reminders-daily/index.ts delete mode 100644 netlify/functions/event-reminders-hourly/index.ts delete mode 100644 netlify/functions/event-reminders-weekly/index.ts delete mode 100644 netlify/functions/slack/index.ts delete mode 100644 netlify/functions/slack/messages.ts delete mode 100644 netlify/functions/zoom-meeting-webhook-handler/airtable.ts delete mode 100644 netlify/functions/zoom-meeting-webhook-handler/index.ts delete mode 100644 netlify/functions/zoom-meeting-webhook-handler/slack.ts delete mode 100644 scripts/build-rooms.ts diff --git a/.env.example b/.env.example index 48c4c18f1..ca5046578 100644 --- a/.env.example +++ b/.env.example @@ -35,24 +35,12 @@ PNPM_FLAGS=--shamefully-hoist # readonly key for membership base: # MEMBERSHIP_AIRTABLE_API_KEY=token # -# Webhooks (Slack / Zoom / co-working bot): +# Redirect functions (/join-coffee and /join-slack in /netlify/functions/): # -# These power the Netlify Functions in /netlify/functions/. Most are only needed if you're -# working on those functions locally — production values live in Netlify's env settings. +# Only needed if you're working on those functions locally — production values live in +# Netlify's env settings. # -# Slack app credentials (https://api.slack.com/apps): -# SLACK_BOT_TOKEN=xoxb-... -# SLACK_SIGNING_SECRET=... -# SLACK_ANNOUNCEMENTS_CHANNEL=C... -# SLACK_EVENT_ADMIN_CHANNEL=C... # SLACK_JOIN_LINK=https://join.slack.com/t/... -# -# Zoom app credentials (https://marketplace.zoom.us/develop/apps): -# ZOOM_WEBHOOK_SECRET_TOKEN=... -# ZOOM_WEBHOOK_AUTH=... # ZOOM_TUESDAYS=https://zoom.us/j/... # ZOOM_THURSDAYS=https://zoom.us/j/... -# -# Airtable base for co-working rooms (used by scripts/build-rooms.ts and the zoom webhook handler): -# AIRTABLE_COWORKING_BASE=app... # \ No newline at end of file diff --git a/README.md b/README.md index 232440272..8e9d6dbe1 100644 --- a/README.md +++ b/README.md @@ -155,24 +155,12 @@ All of the data points have mock data that is used if the required API key isn't If you'd like to work on a feature that requires an API key, please reach out to a maintainer and we can probably get that going. -## Webhooks +## Netlify Functions -Netlify Functions in `netlify/functions/` handle webhook events for the Slack and Zoom integrations, plus scheduled event reminders. Shared utilities and types live in `netlify/functions/_shared/`. +Netlify Functions in `netlify/functions/` provide two short-link redirects (rewrites configured in `netlify.toml`): -HTTP endpoints (rewrites configured in `netlify.toml`): - -- **`/slack-events`** — Slack Events API. Currently handles `team_join` (welcome message) and `app_home_opened` (publishes the welcome view to a member's App Home). -- **`/slack-interactivity`** — Slack interactivity URL. Shares the same handler as `/slack-events`; required to keep buttons in Slack messages working. -- **`/zoom-meeting-webhook-handler`** — Zoom meeting webhooks. Tracks `meeting.{started,ended,participant_joined,participant_left}` for the co-working room and posts/updates Slack messages and Airtable records. -- **`/join-coffee`** and **`/join-slack`** — short-link redirects to the Tuesday/Thursday Zoom rooms and the Slack invite link. - -Scheduled functions (cron schedules declared inline via `export const config`): - -- **`event-reminders-daily`** — `0 12 * * *` (12pm UTC daily). Posts that day's events to the announcements channel; skips Mondays since the weekly reminder runs that day. -- **`event-reminders-hourly`** — `50 * * * *` (50 minutes past every hour). Posts upcoming events starting in the next hour. -- **`event-reminders-weekly`** — `0 12 * * 1` (Monday 12pm UTC). Posts the week's events. - -The `scripts/build-rooms.ts` prebuild step pulls co-working room records from Airtable into `data/rooms.json`, which is bundled into the Zoom function via `included_files` in `netlify.toml`. +- **`/join-coffee`** — redirects to the Tuesday/Thursday Zoom rooms. +- **`/join-slack`** — redirects to the Slack invite link. ## Adding content diff --git a/data/.gitignore b/data/.gitignore deleted file mode 100644 index d6b7ef32c..000000000 --- a/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/netlify.toml b/netlify.toml index b79aa1192..0cd8ba34b 100644 --- a/netlify.toml +++ b/netlify.toml @@ -17,9 +17,6 @@ autoLaunch = false framework="next" -[functions] - included_files = ["data/*.json"] - [[headers]] for = "/_next/static/*" [headers.values] @@ -211,21 +208,6 @@ to = "/.netlify/functions/join-slack" status = 200 -[[redirects]] - from = "/zoom-meeting-webhook-handler" - to = "/.netlify/functions/zoom-meeting-webhook-handler" - status = 200 - -[[redirects]] - from = "/slack-interactivity" - to = "/.netlify/functions/slack" - status = 200 - -[[redirects]] - from = "/slack-events" - to = "/.netlify/functions/slack" - status = 200 - [[redirects]] from = "/plausible/js/script.js" to = "https://plausible.io/js/script.js" diff --git a/netlify/env.d.ts b/netlify/env.d.ts index 1f32cfc7b..362788534 100644 --- a/netlify/env.d.ts +++ b/netlify/env.d.ts @@ -5,27 +5,10 @@ declare namespace NodeJS { CMS_TOKEN?: string; // Slack - SLACK_BOT_TOKEN?: string; - SLACK_SIGNING_SECRET?: string; - SLACK_ANNOUNCEMENTS_CHANNEL?: string; - SLACK_EVENT_ADMIN_CHANNEL?: string; SLACK_JOIN_LINK?: string; // Zoom - ZOOM_WEBHOOK_SECRET_TOKEN?: string; - ZOOM_WEBHOOK_AUTH?: string; ZOOM_TUESDAYS?: string; ZOOM_THURSDAYS?: string; - - // Airtable - AIRTABLE_COWORKING_BASE?: string; - - // Test overrides - TEST_SLACK_BOT_TOKEN?: string; - TEST_SLACK_SIGNING_SECRET?: string; - TEST_SLACK_ANNOUNCEMENTS_CHANNEL?: string; - TEST_SLACK_EVENT_ADMIN_CHANNEL?: string; - TEST_ZOOM_WEBHOOK_SECRET_TOKEN?: string; - TEST_ZOOM_WEBHOOK_AUTH?: string; } } diff --git a/netlify/functions/_shared/slack.ts b/netlify/functions/_shared/slack.ts deleted file mode 100644 index 608395684..000000000 --- a/netlify/functions/_shared/slack.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { webApi } from '@slack/bolt'; - -const SLACK_BOT_TOKEN = - process.env.TEST_SLACK_BOT_TOKEN || process.env.SLACK_BOT_TOKEN; - -const web = new webApi.WebClient(SLACK_BOT_TOKEN); - -export async function postMessage(message: webApi.ChatPostMessageArguments) { - return web.chat.postMessage(message); -} - -export async function updateMessage(message: webApi.ChatUpdateArguments) { - return web.chat.update(message); -} - -export async function publishView(message: webApi.ViewsPublishArguments) { - return web.views.publish(message); -} diff --git a/netlify/functions/_shared/types/cms.ts b/netlify/functions/_shared/types/cms.ts deleted file mode 100644 index f1f3e30d0..000000000 --- a/netlify/functions/_shared/types/cms.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface CalendarsResponse { - solspace_calendar: { - calendars: Array<{ handle: string }>; - }; -} - -export interface CalendarEvent { - id: string; - title: string; - startDateLocalized: string; - endDateLocalized: string; - eventCalendarDescription: string; - eventJoinLink?: string; - eventZoomHostCode?: string; - eventSlackAnnouncementsChannelId?: string; -} - -export interface EventsResponse { - solspace_calendar: { - events: CalendarEvent[]; - }; -} diff --git a/netlify/functions/_shared/types/room.ts b/netlify/functions/_shared/types/room.ts deleted file mode 100644 index 8ce90030f..000000000 --- a/netlify/functions/_shared/types/room.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Room { - ZoomMeetingId: number; - SlackChannelId: string; - ZoomMeetingInviteUrl: string; - MessageSessionStarted: string; - MessageSessionEnded: string; - ButtonJoin: string; - ButtonStartNew: string; - NoticeTitle: string; - NoticeBody: string; - NoticeConfirm: string; - NoticeCancel: string; - ContextBody?: string; - record_id: string; -} diff --git a/netlify/functions/_shared/verify.ts b/netlify/functions/_shared/verify.ts deleted file mode 100644 index 96bfa1b54..000000000 --- a/netlify/functions/_shared/verify.ts +++ /dev/null @@ -1,77 +0,0 @@ -import crypto from 'node:crypto'; - -/** - * Core HMAC-SHA256 verification. Computes `v0=HMAC(secret, message)` and - * performs a timing-safe comparison against the expected signature. - */ -export function verifyHmacSignature( - secret: string, - message: string, - expectedSignature: string, -): boolean { - const computed = - 'v0=' + - crypto.createHmac('sha256', secret).update(message, 'utf8').digest('hex'); - - if (computed.length !== expectedSignature.length) { - return false; - } - - return crypto.timingSafeEqual( - Buffer.from(computed, 'utf8'), - Buffer.from(expectedSignature, 'utf8'), - ); -} - -/** - * Verifies a Slack request signature. Checks timestamp staleness (>300s) - * then validates the HMAC signature. - */ -export function verifySlackRequest( - rawBody: string, - headers: Headers, - secret: string, -): { valid: true } | { valid: false; reason: string } { - const slackSignature = headers.get('x-slack-signature'); - const timestamp = headers.get('x-slack-request-timestamp'); - - const time = Math.floor(Date.now() / 1000); - if (!timestamp || Math.abs(time - Number(timestamp)) > 300) { - return { valid: false, reason: 'Ignore this request.' }; - } - - const message = `v0:${timestamp}:${rawBody}`; - - if (slackSignature && verifyHmacSignature(secret, message, slackSignature)) { - return { valid: true }; - } - - return { valid: false, reason: 'Verification Failed.' }; -} - -/** - * Verifies a Zoom webhook signature using the x-zm-signature header. - */ -export function verifyZoomSignature( - rawBody: string, - headers: Headers, - secret: string, -): boolean { - const zmSignature = headers.get('x-zm-signature'); - const zmTimestamp = headers.get('x-zm-request-timestamp'); - - if (!zmSignature || !zmTimestamp) { - return false; - } - - const message = `v0:${zmTimestamp}:${rawBody}`; - return verifyHmacSignature(secret, message, zmSignature); -} - -/** - * Computes an HMAC-SHA256 hex digest. Used for Zoom's endpoint URL - * validation challenge response. - */ -export function hmacSha256Hex(secret: string, data: string): string { - return crypto.createHmac('sha256', secret).update(data).digest('hex'); -} diff --git a/netlify/functions/event-reminders-daily/index.ts b/netlify/functions/event-reminders-daily/index.ts deleted file mode 100644 index beab76cac..000000000 --- a/netlify/functions/event-reminders-daily/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { GraphQLClient, gql } from 'graphql-request'; -import { DateTime } from 'luxon'; -import { postMessage } from '../_shared/slack'; -import slackify from 'slackify-html'; -import { requireEnv } from '../_shared/env'; -import type { Config } from '@netlify/functions'; -import type { CalendarsResponse, EventsResponse } from '../_shared/types/cms'; - -const SLACK_ANNOUNCEMENTS_CHANNEL = - process.env.TEST_SLACK_ANNOUNCEMENTS_CHANNEL || - requireEnv('SLACK_ANNOUNCEMENTS_CHANNEL'); - -const DEFAULT_SLACK_EVENT_CHANNEL = 'C017WAKN883'; - -const calendarsQuery = gql` - query getCalendars { - solspace_calendar { - calendars { - handle - } - } - } -`; - -function createEventsQuery(calendars: CalendarsResponse) { - return gql` - query getEvents($rangeStart: String!, $rangeEnd: String!) { - solspace_calendar { - events(rangeStart: $rangeStart, rangeEnd: $rangeEnd) { - id - title - startDateLocalized - endDateLocalized - ${calendars.solspace_calendar.calendars.map( - ({ handle }) => ` - ... on ${handle}_Event { - eventCalendarDescription - eventJoinLink - eventZoomHostCode - eventSlackAnnouncementsChannelId - id - } - `, - )} - } - } - } -`; -} - -export default async (req: Request) => { - const graphQLClient = new GraphQLClient(`${process.env.CMS_URL}/api`, { - headers: { - Authorization: `bearer ${process.env.CMS_TOKEN}`, - }, - }); - - const rangeStart = DateTime.now().setZone('America/New_York').toISO(); - const rangeEnd = DateTime.now() - .setZone('America/New_York') - .plus({ days: 1 }) - .toISO(); - - console.log('Fetching events', rangeStart, rangeEnd); - - try { - const calendarsResponse = - await graphQLClient.request(calendarsQuery); - - const eventsResponse = await graphQLClient.request( - createEventsQuery(calendarsResponse), - { - rangeStart, - rangeEnd, - }, - ); - - const eventsList = eventsResponse.solspace_calendar.events; - if (eventsList && eventsList.length) { - const dayCheck = new Date(); - if (dayCheck.getDay() === 1) { - // don't run this one on monday, since the weekly one runs on monday - return; - } - - const dailyMessage = { - channel: SLACK_ANNOUNCEMENTS_CHANNEL, - text: `Today's events are: ${eventsList - .map((event) => { - return `${event.title}: ${DateTime.fromISO( - event.startDateLocalized, - ).toFormat('EEEE, fff')}`; - }) - .join(', ')}`, - unfurl_links: false, - unfurl_media: false, - blocks: [ - { - type: 'header' as const, - text: { - type: 'plain_text' as const, - text: "📆 Today's Events Are:", - emoji: true, - }, - }, - ...eventsList.reduce[]>((list, event) => { - const eventDate = DateTime.fromISO(event.startDateLocalized); - return [ - ...list, - { - type: 'section', - text: { - type: 'mrkdwn', - text: `*${ - event.title - }*\n`, - }, - }, - { - type: 'context', - elements: [ - { - type: 'mrkdwn', - text: slackify(event.eventCalendarDescription), - }, - ], - }, - { - type: 'context', - elements: [ - { - type: 'mrkdwn', - text: `ℹ️ Link to join will be posted in <#${ - event.eventSlackAnnouncementsChannelId || - DEFAULT_SLACK_EVENT_CHANNEL - }> about 10 minutes before the event starts.`, - }, - ], - }, - { - type: 'divider', - }, - ]; - }, []), - ], - }; - - await postMessage(dailyMessage); - } - - return new Response(null, { status: 200 }); - } catch (e) { - console.error(e); - return new Response(null, { status: 500 }); - } -}; - -export const config: Config = { - schedule: '0 12 * * *', -}; diff --git a/netlify/functions/event-reminders-hourly/index.ts b/netlify/functions/event-reminders-hourly/index.ts deleted file mode 100644 index 186a6635d..000000000 --- a/netlify/functions/event-reminders-hourly/index.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { GraphQLClient, gql } from 'graphql-request'; -import { DateTime } from 'luxon'; -import { postMessage } from '../_shared/slack'; -import slackify from 'slackify-html'; -import { requireEnv } from '../_shared/env'; -import type { Config } from '@netlify/functions'; -import type { CalendarsResponse, EventsResponse } from '../_shared/types/cms'; - -const SLACK_ANNOUNCEMENTS_CHANNEL = - process.env.TEST_SLACK_ANNOUNCEMENTS_CHANNEL || - requireEnv('SLACK_ANNOUNCEMENTS_CHANNEL'); - -const SLACK_EVENT_ADMIN_CHANNEL = - process.env.TEST_SLACK_EVENT_ADMIN_CHANNEL || - requireEnv('SLACK_EVENT_ADMIN_CHANNEL'); - -const DEFAULT_SLACK_EVENT_CHANNEL = 'C017WAKN883'; - -const calendarsQuery = gql` - query getCalendars { - solspace_calendar { - calendars { - handle - } - } - } -`; - -function createEventsQuery(calendars: CalendarsResponse) { - return gql` - query getEvents($rangeStart: String!, $rangeEnd: String!) { - solspace_calendar { - events(rangeStart: $rangeStart, rangeEnd: $rangeEnd) { - id - title - startDateLocalized - endDateLocalized - ${calendars.solspace_calendar.calendars.map( - ({ handle }) => ` - ... on ${handle}_Event { - eventCalendarDescription - eventJoinLink - eventZoomHostCode - id - eventSlackAnnouncementsChannelId - } - `, - )} - } - } - } -`; -} - -export default async (req: Request) => { - const graphQLClient = new GraphQLClient(`${process.env.CMS_URL}/api`, { - headers: { - Authorization: `bearer ${process.env.CMS_TOKEN}`, - }, - }); - - const rangeStart = DateTime.now() - .setZone('America/New_York') - .set({ hour: 0 }) - .toISO(); - const rangeEnd = DateTime.now() - .setZone('America/New_York') - .plus({ hours: 1 }) - .toISO(); - - console.log('Fetching events', rangeStart, rangeEnd); - - try { - const calendarsResponse = - await graphQLClient.request(calendarsQuery); - - const eventsResponse = await graphQLClient.request( - createEventsQuery(calendarsResponse), - { - rangeStart, - rangeEnd, - }, - ); - - const eventsList = eventsResponse.solspace_calendar.events; - if (eventsList && eventsList.length) { - // filter out past events - const now = DateTime.now(); - const filteredList = eventsList.filter((event) => { - return now < DateTime.fromISO(event.startDateLocalized); - }); - - if (filteredList.length) { - const hourlyMessages = filteredList.map((event) => { - const eventDate = DateTime.fromISO(event.startDateLocalized); - - const blocks: Record[] = [ - { - type: 'header', - text: { - type: 'plain_text', - text: '⏰ Starting Soon:', - emoji: true, - }, - }, - ]; - - const titleBlock: Record = { - type: 'section', - text: { - type: 'mrkdwn', - text: `*${ - event.title - }*\n`, - }, - }; - - if ( - event.eventJoinLink && - event.eventJoinLink.substring(0, 4) === 'http' - ) { - titleBlock.accessory = { - type: 'button', - text: { - type: 'plain_text', - text: 'Join Event', - emoji: true, - }, - value: `join_event_${event.id}`, - url: event.eventJoinLink, - action_id: 'button-join-event', - }; - } - - blocks.push(titleBlock); - - if ( - event.eventJoinLink && - event.eventJoinLink.substring(0, 4) !== 'http' - ) { - blocks.push({ - type: 'section', - text: { - type: 'mrkdwn', - text: `*Location:* ${event.eventJoinLink}`, - }, - }); - } - - blocks.push( - { - type: 'context', - elements: [ - { - type: 'mrkdwn', - text: slackify(event.eventCalendarDescription), - }, - ], - }, - { - type: 'divider', - }, - ); - - return { - channel: - event.eventSlackAnnouncementsChannelId || - DEFAULT_SLACK_EVENT_CHANNEL, - text: `Starting soon: ${event.title}: ${eventDate.toFormat( - 'EEEE, fff', - )}`, - unfurl_links: false, - unfurl_media: false, - blocks, - }; - }); - - const hourlyAdminMessage = { - channel: SLACK_EVENT_ADMIN_CHANNEL, - text: `Starting soon: ${filteredList - .map((event) => { - return `${event.title}: ${DateTime.fromISO( - event.startDateLocalized, - ).toFormat('EEEE, fff')}`; - }) - .join(', ')}`, - unfurl_links: false, - unfurl_media: false, - blocks: [ - { - type: 'header' as const, - text: { - type: 'plain_text' as const, - text: '⏰ Starting Soon:', - emoji: true, - }, - }, - ...filteredList.reduce[]>((list, event) => { - const eventDate = DateTime.fromISO(event.startDateLocalized); - - const titleBlock: Record = { - type: 'section', - text: { - type: 'mrkdwn', - text: `*${ - event.title - }*\n`, - }, - }; - - if ( - event.eventJoinLink && - event.eventJoinLink.substring(0, 4) === 'http' - ) { - titleBlock.accessory = { - type: 'button', - text: { - type: 'plain_text', - text: 'Join Event', - emoji: true, - }, - value: `join_event_${event.id}`, - url: event.eventJoinLink, - action_id: 'button-join-event', - }; - } - - return [ - ...list, - titleBlock, - { - type: 'section', - text: { - type: 'mrkdwn', - text: `*Location:* ${event.eventJoinLink}`, - }, - }, - ...(event.eventZoomHostCode - ? [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: `*Host Code:* ${event.eventZoomHostCode}`, - }, - }, - ] - : []), - { - type: 'section', - text: { - type: 'mrkdwn', - text: `*Announcement posted to:* <#${ - event.eventSlackAnnouncementsChannelId || - DEFAULT_SLACK_EVENT_CHANNEL - }>`, - }, - }, - { - type: 'divider', - }, - ]; - }, []), - ], - }; - - await postMessage(hourlyAdminMessage); - - await Promise.all( - hourlyMessages.map((message) => postMessage(message)), - ); - } - } - return new Response(null, { status: 200 }); - } catch (e) { - console.error(e); - return new Response(null, { status: 500 }); - } -}; - -export const config: Config = { - schedule: '50 * * * *', -}; diff --git a/netlify/functions/event-reminders-weekly/index.ts b/netlify/functions/event-reminders-weekly/index.ts deleted file mode 100644 index c446d4c9c..000000000 --- a/netlify/functions/event-reminders-weekly/index.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { GraphQLClient, gql } from 'graphql-request'; -import { DateTime } from 'luxon'; -import { postMessage } from '../_shared/slack'; -import { requireEnv } from '../_shared/env'; -import type { Config } from '@netlify/functions'; -import type { CalendarsResponse, EventsResponse } from '../_shared/types/cms'; - -const SLACK_ANNOUNCEMENTS_CHANNEL = - process.env.TEST_SLACK_ANNOUNCEMENTS_CHANNEL || - requireEnv('SLACK_ANNOUNCEMENTS_CHANNEL'); - -const DEFAULT_SLACK_EVENT_CHANNEL = 'C017WAKN883'; - -const calendarsQuery = gql` - query getCalendars { - solspace_calendar { - calendars { - handle - } - } - } -`; - -function createEventsQuery(calendars: CalendarsResponse) { - return gql` - query getEvents($rangeStart: String!, $rangeEnd: String!) { - solspace_calendar { - events(rangeStart: $rangeStart, rangeEnd: $rangeEnd) { - id - title - startDateLocalized - endDateLocalized - ${calendars.solspace_calendar.calendars.map( - ({ handle }) => ` - ... on ${handle}_Event { - eventCalendarDescription - eventJoinLink - eventZoomHostCode - eventSlackAnnouncementsChannelId - id - } - `, - )} - } - } - } -`; -} - -export default async (req: Request) => { - const graphQLClient = new GraphQLClient(`${process.env.CMS_URL}/api`, { - headers: { - Authorization: `bearer ${process.env.CMS_TOKEN}`, - }, - }); - - const rangeStart = DateTime.now() - .setZone('America/New_York') - .set({ hour: 0 }) - .toISO(); - const rangeEnd = DateTime.now() - .setZone('America/New_York') - .plus({ weeks: 1 }) - .toISO(); - - console.log('Fetching events', rangeStart, rangeEnd); - - try { - const calendarsResponse = - await graphQLClient.request(calendarsQuery); - - const eventsResponse = await graphQLClient.request( - createEventsQuery(calendarsResponse), - { - rangeStart, - rangeEnd, - }, - ); - - const eventsList = eventsResponse.solspace_calendar.events; - if (eventsList && eventsList.length) { - const weeklyMessage = { - channel: SLACK_ANNOUNCEMENTS_CHANNEL, - text: `This weeks events are: ${eventsList - .map((event) => { - return `${event.title}: ${DateTime.fromISO( - event.startDateLocalized, - ).toFormat('EEEE, fff')}`; - }) - .join(', ')}`, - unfurl_links: false, - unfurl_media: false, - blocks: [ - { - type: 'header' as const, - text: { - type: 'plain_text' as const, - text: "📆 This Week's Events Are:", - emoji: true, - }, - }, - ...eventsList.map((event) => { - const eventDate = DateTime.fromISO(event.startDateLocalized); - // TODO - colate these by date - return { - type: 'section' as const, - text: { - type: 'mrkdwn' as const, - text: `** in <#${ - event.eventSlackAnnouncementsChannelId || - DEFAULT_SLACK_EVENT_CHANNEL - }>\n${event.title}`, - }, - }; - }), - { - type: 'context' as const, - elements: [ - { - type: 'mrkdwn' as const, - text: `ℹ️ Links to join will be posted in the specified channel about 10 minutes before the event starts.`, - }, - ], - }, - { - type: 'divider' as const, - }, - { - type: 'context' as const, - elements: [ - { - type: 'mrkdwn' as const, - text: `See details and more events at !`, - }, - ], - }, - ], - }; - - await postMessage(weeklyMessage); - } - return new Response(null, { status: 200 }); - } catch (e) { - console.error(e); - return new Response(null, { status: 500 }); - } -}; - -export const config: Config = { - schedule: '0 12 * * 1', -}; diff --git a/netlify/functions/slack/index.ts b/netlify/functions/slack/index.ts deleted file mode 100644 index 702a4c525..000000000 --- a/netlify/functions/slack/index.ts +++ /dev/null @@ -1,132 +0,0 @@ -import querystring from 'node:querystring'; -import { App } from '@slack/bolt'; -import type { Receiver, ReceiverEvent } from '@slack/bolt'; -import { verifySlackRequest } from '../_shared/verify'; -import { requireEnv } from '../_shared/env'; -import * as messages from './messages'; - -const SLACK_BOT_TOKEN = - process.env.TEST_SLACK_BOT_TOKEN || requireEnv('SLACK_BOT_TOKEN'); -const SLACK_SIGNING_SECRET = - process.env.TEST_SLACK_SIGNING_SECRET || requireEnv('SLACK_SIGNING_SECRET'); - -/** - * Custom Bolt receiver for Netlify Functions. - * Handles signature verification, body parsing (JSON + form-encoded), - * url_verification challenges, and ReceiverEvent construction. - */ -class NetlifyReceiver implements Receiver { - private app?: App; - private signingSecret: string; - - constructor(signingSecret: string) { - this.signingSecret = signingSecret; - } - - init(app: App) { - this.app = app; - } - - start() { - return Promise.resolve(); - } - - stop() { - return Promise.resolve(); - } - - async handleRequest(req: Request): Promise { - const rawBody = await req.text(); - const contentType = req.headers.get('content-type') ?? ''; - const body = this.parseBody(rawBody, contentType); - - // Slack sends this once when configuring the Events API URL - if (body.type === 'url_verification') { - return new Response(JSON.stringify({ challenge: body.challenge }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }); - } - - const isValid = verifySlackRequest( - rawBody, - req.headers, - this.signingSecret, - ); - if (!isValid.valid) { - console.log('Failed validation:', isValid.reason); - return new Response(isValid.reason, { status: 401 }); - } - - let storedResponse: string | undefined; - - const event: ReceiverEvent = { - body, - ack: async (response) => { - if (typeof response === 'undefined' || response == null) { - storedResponse = ''; - } else if (typeof response === 'string') { - storedResponse = response; - } else { - storedResponse = JSON.stringify(response); - } - }, - retryNum: req.headers.get('x-slack-retry-num') - ? Number(req.headers.get('x-slack-retry-num')) - : undefined, - retryReason: req.headers.get('x-slack-retry-reason') ?? undefined, - }; - - try { - await this.app!.processEvent(event); - - if (storedResponse !== undefined) { - return new Response(storedResponse, { - status: 200, - headers: storedResponse - ? { 'Content-Type': 'application/json' } - : undefined, - }); - } - } catch (error) { - console.error('Error processing Slack event:', error); - return new Response('Internal server error', { status: 500 }); - } - - return new Response('', { status: 404 }); - } - - private parseBody( - rawBody: string, - contentType: string, - ): Record { - if (contentType.includes('application/x-www-form-urlencoded')) { - const parsed = querystring.parse(rawBody); - if (typeof parsed.payload === 'string') { - return JSON.parse(parsed.payload); - } - return parsed; - } - return JSON.parse(rawBody); - } -} - -const receiver = new NetlifyReceiver(SLACK_SIGNING_SECRET); - -const app = new App({ - token: SLACK_BOT_TOKEN, - receiver, - processBeforeResponse: true, -}); - -app.event('team_join', async ({ event, client }) => { - const msg = messages.welcome({ event }); - await client.chat.postMessage(msg); -}); - -app.event('app_home_opened', async ({ event, client }) => { - const view = messages.appHome({ event }); - await client.views.publish(view); -}); - -export default async (req: Request) => receiver.handleRequest(req); diff --git a/netlify/functions/slack/messages.ts b/netlify/functions/slack/messages.ts deleted file mode 100644 index 00ae8402e..000000000 --- a/netlify/functions/slack/messages.ts +++ /dev/null @@ -1,161 +0,0 @@ -interface SlackUser { - id: string; - name: string; -} - -interface TeamJoinEvent { - user: SlackUser; -} - -interface AppHomeOpenedEvent { - user: string; -} - -function getWelcomeBlocks(user?: SlackUser) { - return [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: `:wave: Hey ${ - user ? `@${user.name}` : `there` - }, welcome to Virtual Coffee -- fondly referred to as VC around this space.`, - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ' ', - }, - }, - { - type: 'divider', - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ' ', - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ":heart: Before doing anything else, please first take a moment to read our . Our Code of Conduct is in effect at any Virtual Coffee function, including direct messages. If you have experienced or witnessed violations to Virtual Coffee's Code of Conduct, please use our to let us know.", - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ' ', - }, - }, - { - type: 'divider', - }, - { - type: 'header', - text: { - type: 'plain_text', - text: 'Now for the fun part!', - emoji: true, - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'We have a lot going on here, but here are some places you might want to start:', - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: `:white_check_mark: Head over to #welcome and introduce yourself to the rest of the group! Let us know what you like to do in your freetime, what you're doing in tech, and a random fact about your life!`, - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ':dart: Check out #monthly-challenge to see what the community is working on together right now.', - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ':mega: The #announcements channel has the most recent news on events and initiatives happening in the community.', - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ":computer: Our #co-working-room is a zoom room that's open all day, every day for members to quietly work, pair on solving problems, or just say hello.", - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ':question: #help-and-pairing is the space for asking questions about any and all tech related topics. But if you have a general question, we have a really welcoming community, so feel free to throw it in the channel that looks best.', - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ' ', - }, - }, - { - type: 'divider', - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: ' ', - }, - }, - // Member ids (use ids in case username changes): - // - Julia: U01JXQGMSUC - // - Kirk: U01577R42TS - // - Bekah: U014HT3RNCU - // - Dan: U0157K5MUPJ - // - Meg: U01B9NQF2PR - { - type: 'section', - text: { - type: 'mrkdwn', - text: ":heart: And remember, you can always message one of our community maintainers, <@U014HT3RNCU>, <@U0157K5MUPJ>, <@U01577R42TS>, <@U01JXQGMSUC>, or <@U01B9NQF2PR> for any help and support you may need. \n\n *We're happy to have you here!*", - }, - }, - ]; -} - -export function appHome({ event }: { event: AppHomeOpenedEvent }) { - return { - user_id: event.user, - view: { - type: 'home' as const, - blocks: getWelcomeBlocks(), - }, - }; -} - -export function welcome({ event }: { event: TeamJoinEvent }) { - return { - link_names: true, - unfurl_links: false, - unfurl_media: false, - channel: event.user.id, - text: `:wave: Hey @${event.user.name}, welcome to Virtual Coffee -- fondly referred to as VC around this space.`, - blocks: getWelcomeBlocks(event.user), - }; -} diff --git a/netlify/functions/zoom-meeting-webhook-handler/airtable.ts b/netlify/functions/zoom-meeting-webhook-handler/airtable.ts deleted file mode 100644 index b13c61838..000000000 --- a/netlify/functions/zoom-meeting-webhook-handler/airtable.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Room } from '../_shared/types/room'; -import type Airtable from 'airtable'; - -type AirtableBase = ReturnType['base']>; - -// returns a roomInstance record, or undefined. -// Will retry 5 times, pausing 1 second between tries. -export async function findRoomInstance( - room: Room, - base: AirtableBase, - instanceId: string, -) { - async function tryFind() { - const resultArray = await base('room_instances') - .select({ - // Selecting the first 1 records in Grid view: - maxRecords: 1, - view: 'Grid view', - filterByFormula: `AND(RoomZoomMeetingId='${room.ZoomMeetingId}',instance_uuid='${instanceId}')`, - }) - .firstPage(); - - return resultArray[0]; - } - function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - let roomInstance = await tryFind(); - let count = 0; - while (count < 5 && !roomInstance) { - count++; - await sleep(400 * count); - roomInstance = await tryFind(); - } - - if (!roomInstance) { - console.log(`room instance ${instanceId} not found`); - } - - return roomInstance; -} diff --git a/netlify/functions/zoom-meeting-webhook-handler/index.ts b/netlify/functions/zoom-meeting-webhook-handler/index.ts deleted file mode 100644 index 5a1968910..000000000 --- a/netlify/functions/zoom-meeting-webhook-handler/index.ts +++ /dev/null @@ -1,165 +0,0 @@ -import Airtable from 'airtable'; -import { updateMeetingStatus, updateMeetingAttendance } from './slack'; -import { findRoomInstance } from './airtable'; -import { requireEnv } from '../_shared/env'; -import { verifyZoomSignature, hmacSha256Hex } from '../_shared/verify'; -import type { Room } from '../_shared/types/room'; - -import rooms from '../../../data/rooms.json' with { type: 'json' }; -const typedRooms = rooms as Room[]; - -const EVENT_MEETING_STARTED = 'meeting.started'; -const EVENT_MEETING_ENDED = 'meeting.ended'; -const EVENT_PARTICIPANT_JOINED = 'meeting.participant_joined'; -const EVENT_PARTICIPANT_LEFT = 'meeting.participant_left'; - -const ZOOM_SECRET = - process.env.TEST_ZOOM_WEBHOOK_SECRET_TOKEN || - requireEnv('ZOOM_WEBHOOK_SECRET_TOKEN'); - -const ZOOM_AUTH = - process.env.TEST_ZOOM_WEBHOOK_AUTH || requireEnv('ZOOM_WEBHOOK_AUTH'); - -export default async (req: Request) => { - try { - const rawBody = await req.text(); - - /** - * verification. zoom will either send an authorization header or a x-zm-signature header - */ - - const authorized = - verifyZoomSignature(rawBody, req.headers, ZOOM_SECRET) || - req.headers.get('authorization') === ZOOM_AUTH; - - if (!authorized) { - console.log('Unauthorized'); - return new Response('', { status: 401 }); - } - - const request = JSON.parse(rawBody); - - if (request.event == 'endpoint.url_validation') { - const hashForValidate = hmacSha256Hex( - ZOOM_SECRET, - request.payload.plainToken, - ); - return new Response( - JSON.stringify({ - plainToken: request.payload.plainToken, - encryptedToken: hashForValidate, - }), - { status: 200 }, - ); - } - - // check our meeting ID. The meeting ID never changes, but the uuid is different for each instance - - const room = typedRooms.find( - (room) => room.ZoomMeetingId === request.payload.object.id, - ); - console.log('incoming request'); - console.log('request payload'); - console.log(request.payload.object); - console.log('request event'); - console.log(request.event); - - if (room) { - const base = new Airtable().base(requireEnv('AIRTABLE_COWORKING_BASE')); - - switch (request.event) { - case EVENT_PARTICIPANT_JOINED: - case EVENT_PARTICIPANT_LEFT: { - const roomInstance = await findRoomInstance( - room, - base, - request.payload.object.uuid, - ); - - if (roomInstance) { - // create room event record - console.log(`found room instance ${roomInstance.getId()}`); - - await updateMeetingAttendance( - room, - roomInstance.get('slack_thread_timestamp') as string, - request, - ); - } - - break; - } - - case EVENT_MEETING_STARTED: { - // post message to Slack and get result - console.log('posting update'); - const result = await updateMeetingStatus(room); - console.log('done posting update'); - - // create new room instance - const created = await base('room_instances').create({ - instance_uuid: request.payload.object.uuid, - slack_thread_timestamp: result.ts, - start_time: request.payload.object.start_time, - room_record: [room.record_id], - }); - - if (!created) { - throw new Error('no record created'); - } - - console.log(`room_event created: ${created.getId()}`); - - break; - } - - case EVENT_MEETING_ENDED: { - const roomInstanceEnd = await findRoomInstance( - room, - base, - request.payload.object.uuid, - ); - - if (roomInstanceEnd) { - await updateMeetingStatus( - room, - roomInstanceEnd.get('slack_thread_timestamp') as string, - ); - - // update room instance - const updated = await base('room_instances').update( - roomInstanceEnd.getId(), - { - end_time: request.payload.object.end_time, - }, - ); - - if (!updated) { - throw new Error('no record updated'); - } - - console.log(`room_event updated: ${updated.getId()}`); - } - - break; - } - - default: - break; - } - } else { - console.log('meeting ID is not co-working meeting'); - } - - return new Response('', { status: 200 }); - } catch (error) { - // output to netlify function log - console.log(error); - return new Response( - JSON.stringify({ - msg: error instanceof Error ? error.message : String(error), - }), - { status: 500 }, - ); - } -}; diff --git a/netlify/functions/zoom-meeting-webhook-handler/slack.ts b/netlify/functions/zoom-meeting-webhook-handler/slack.ts deleted file mode 100644 index 33ff87638..000000000 --- a/netlify/functions/zoom-meeting-webhook-handler/slack.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { postMessage, updateMessage } from '../_shared/slack'; -import type { Room } from '../_shared/types/room'; - -interface ZoomWebhookRequest { - event: string; - payload: { - object: { - participant: { - user_name: string; - }; - }; - }; -} - -// timestamp: if we have a timestamp, that means we've ended the meeting and are trying to update the message -// otherwise, post a new message - -export async function updateMeetingStatus(room: Room, timestamp?: string) { - const message = { - channel: room.SlackChannelId, - text: timestamp ? room.MessageSessionEnded : room.MessageSessionStarted, - unfurl_links: false, - unfurl_media: false, - blocks: [ - { - type: 'section' as const, - text: { - type: 'mrkdwn' as const, - text: timestamp - ? room.MessageSessionEnded - : room.MessageSessionStarted, - }, - accessory: { - type: 'button' as const, - text: { - type: 'plain_text' as const, - text: timestamp ? room.ButtonStartNew : room.ButtonJoin, - emoji: true, - }, - value: 'join_meeting', - url: room.ZoomMeetingInviteUrl, - action_id: 'button-action', - style: 'primary' as const, - confirm: { - title: { - type: 'plain_text' as const, - text: room.NoticeTitle, - }, - text: { - type: 'mrkdwn' as const, - text: room.NoticeBody, - }, - confirm: { - type: 'plain_text' as const, - text: room.NoticeConfirm, - }, - deny: { - type: 'plain_text' as const, - text: room.NoticeCancel, - }, - }, - }, - }, - ...(room.ContextBody - ? [ - { - type: 'context' as const, - elements: [ - { - type: 'mrkdwn' as const, - text: room.ContextBody, - }, - ], - }, - ] - : []), - ], - }; - - // These calls never use background mode, so the result is always a Slack API response - const result = timestamp - ? await updateMessage({ ...message, ts: timestamp }) - : await postMessage(message); - - const slackResult = result as { ts?: string }; - - console.log( - `Successfully send message ${slackResult.ts} in conversation ${room.SlackChannelId}`, - ); - - return slackResult; -} - -export async function updateMeetingAttendance( - room: Room, - thread_ts: string, - zoomRequest: ZoomWebhookRequest, -) { - const username = zoomRequest.payload.object.participant.user_name; - const result = await postMessage({ - thread_ts, - text: - zoomRequest.event === 'meeting.participant_joined' - ? `${username} has joined!` - : `${username} has left. We'll miss you!`, - channel: room.SlackChannelId, - }); - - const slackResult = result as { ts?: string }; - - console.log( - `Successfully send message ${slackResult.ts} in conversation ${room.SlackChannelId}`, - ); - - return slackResult; -} diff --git a/package.json b/package.json index df7fd359c..d3aca0c07 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,10 @@ "format": "npx prettier --write --ignore-unknown --list-different \"**/*\"", "lint": "eslint", "build-member-files": "tsx scripts/loadMemberFiles.js", - "build-rooms": "tsx scripts/build-rooms.ts", "local-dev": "cross-env NODE_ENV=development pnpm netlify dev", "build": "next build", "start": "next start", - "prebuild": "pnpm build-member-files && pnpm build-rooms", - "pretypecheck": "pnpm build-rooms", + "prebuild": "pnpm build-member-files", "typecheck": "tsc --noEmit", "watch": "npm-watch", "dev": "pnpm build-member-files && concurrently \"pnpm watch\" \"pnpm local-dev\"" @@ -31,10 +29,8 @@ "@imgix/js-core": "^3.8.0", "@mdx-js/loader": "^3.1.1", "@mdx-js/react": "^3.1.1", - "@netlify/functions": "^5.1.5", "@next/mdx": "15.5.18", "@sindresorhus/slugify": "^2.2.1", - "@slack/bolt": "^4.6.0", "@types/mdx": "^2.0.13", "airtable": "^0.12.2", "bootstrap": "^4.6.2", @@ -66,7 +62,6 @@ "remark-rehype": "^10.1.0", "require-dir": "^1.2.0", "sanitize-html": "^2.17.0", - "slackify-html": "^1.0.1", "unified": "^10.1.2" }, "devDependencies": { @@ -80,11 +75,9 @@ "@types/react-dom": "^19.2.3", "@types/require-dir": "^1.0.4", "@types/sanitize-html": "^2.16.0", - "@types/slackify-html": "^1.0.8", "all-contributors-cli": "^6.26.1", "concurrently": "^9.2.1", "cross-env": "^10.1.0", - "dotenv": "^16.0.2", "esbuild": "^0.25.12", "eslint": "^9.39.2", "eslint-config-next": "15.5.18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc41e39b0..1e440b7a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,18 +26,12 @@ importers: '@mdx-js/react': specifier: ^3.1.1 version: 3.1.1(@types/react@19.2.7)(react@19.2.3) - '@netlify/functions': - specifier: ^5.1.5 - version: 5.2.0 '@next/mdx': specifier: 15.5.18 version: 15.5.18(@mdx-js/loader@3.1.1(webpack@5.102.0(esbuild@0.25.12)(postcss@8.5.10)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3)) '@sindresorhus/slugify': specifier: ^2.2.1 version: 2.2.1 - '@slack/bolt': - specifier: ^4.6.0 - version: 4.7.2 '@types/mdx': specifier: ^2.0.13 version: 2.0.13 @@ -131,9 +125,6 @@ importers: sanitize-html: specifier: ^2.17.0 version: 2.17.0 - slackify-html: - specifier: ^1.0.1 - version: 1.0.1 unified: specifier: ^10.1.2 version: 10.1.2 @@ -168,9 +159,6 @@ importers: '@types/sanitize-html': specifier: ^2.16.0 version: 2.16.0 - '@types/slackify-html': - specifier: ^1.0.8 - version: 1.0.8 all-contributors-cli: specifier: ^6.26.1 version: 6.26.1 @@ -180,9 +168,6 @@ importers: cross-env: specifier: ^10.1.0 version: 10.1.0 - dotenv: - specifier: ^16.0.2 - version: 16.6.1 esbuild: specifier: ^0.25.12 version: 0.25.12 @@ -1155,10 +1140,6 @@ packages: resolution: {integrity: sha512-28hNylsGcdxq6fngFXIShxVhamzw2K5twEx7pQaYFDjj52OLHjd0L+vPZ9HYUaeOoHuJgq8ir3RoVgITwcY+3g==} engines: {node: '>=18.14.0'} - '@netlify/functions@5.2.0': - resolution: {integrity: sha512-Pj93qeQd1tkQ5xm9gWJZmBf/1riLYqYHc0OzFukrJomrj82Ott53Rr/Q88H1ms5cF+P5QXRKWmA2JSxSybKfjA==} - engines: {node: '>=18.0.0'} - '@netlify/git-utils@6.0.2': resolution: {integrity: sha512-ASp8T6ZAxL5OE0xvTTn5+tIBua5F8ruLH7oYtI/m2W/8rYb9V3qvNeenf9SnKlGj1xv6mPv8l7Tc93kmBLLofw==} engines: {node: '>=18.14.0'} @@ -1289,10 +1270,6 @@ packages: resolution: {integrity: sha512-XOWlZ2wPpdRKkAOcQbjIf/Qz7L4RjcSVINVNQ9p3F6U8V6KSEOsB3fPrc6Ly8EOeJioHUepRPuzHzJE/7V5EsA==} engines: {node: ^18.14.0 || >=20} - '@netlify/types@2.6.0': - resolution: {integrity: sha512-yD20EizHJDQxajJ66Vo8RTwLwR2jMNVxufPG8MHd2AScX8jW4z0VPnnJHArq2GYPFTFZRHmiAhDrXr5m8zof6w==} - engines: {node: ^18.14.0 || >=20} - '@netlify/zip-it-and-ship-it@14.1.14': resolution: {integrity: sha512-33w50VcYLZ7RpUCFvl+n8JoLRGSVKerbH6cXtVjzA7un9JSkJWZQVS3nDmWYbq6OR0VnS1LGn7r+/ll6pSOvCg==} engines: {node: '>=18.14.0'} @@ -1658,32 +1635,6 @@ packages: resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} engines: {node: '>=12'} - '@slack/bolt@4.7.2': - resolution: {integrity: sha512-ALHtaS2iaP2WAWgX08yXsoCxEDitC6AqZs26ot6smXJQzBFMM4slVP+w3blLwzUV551xZ/+9RlBmWHsZDJJ5HA==} - engines: {node: '>=18', npm: '>=8.6.0'} - 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/oauth@3.0.5': - resolution: {integrity: sha512-exqFQySKhNDptWYSWhvRUJ4/+ndu2gayIy7vg/JfmJq3wGtGdHk531P96fAZyBm5c1Le3yaPYqv92rL4COlU3A==} - engines: {node: '>=18', npm: '>=8.6.0'} - - '@slack/socket-mode@2.0.7': - resolution: {integrity: sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==} - engines: {node: '>= 18', npm: '>= 8.6.0'} - - '@slack/types@2.21.0': - resolution: {integrity: sha512-ZLMsKnD5KLRPmhFEoGoBQUD5Pc2bH3xFc5ygHlioEc0WmLGyZGoGCtMff4rpejrFnptrhfxcKpWxW4r3g39R0A==} - engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} - - '@slack/web-api@7.15.2': - resolution: {integrity: sha512-/m9qVFkiq85Oa/FSQwYIRDa/AO4qNYkDh4sRBK1WqEc2+RyG7w4tbU6rBIwUOcc/TmWOIr24Nraquxg7um5mYw==} - engines: {node: '>= 18', npm: '>= 8.6.0'} - '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} @@ -1751,9 +1702,6 @@ packages: '@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/leaflet.markercluster@1.5.6': resolution: {integrity: sha512-I7hZjO2+isVXGYWzKxBp8PsCzAYCJBc29qBdFpquOCkS7zFDqUsUvkEOyQHedsk/Cy5tocQzf+Ndorm5W9YKTQ==} @@ -1801,18 +1749,12 @@ packages: '@types/require-dir@1.0.4': resolution: {integrity: sha512-wZlfKxz4F97pMVAa9ztjm0TnaK4rH0COO9EE7AkMbvuIcXkrlFZQm2naZpITFN+PBqJ8vgxPV2+M6TzxWUQUSg==} - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} '@types/sanitize-html@2.16.0': resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==} - '@types/slackify-html@1.0.8': - resolution: {integrity: sha512-wA1YZkD/MyxXfLphMiUPOqPQOVle31z6sQFa0lQFTZUT3QbgmWKOenh4oetheasCt+Kmi/oh5VrdAEM/EgaXSQ==} - '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} @@ -1825,9 +1767,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -2151,10 +2090,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -2366,9 +2301,6 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -2387,9 +2319,6 @@ packages: resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} engines: {node: '>=4'} - axios@1.16.0: - resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} - axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -2455,10 +2384,6 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.2: - resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} - engines: {node: '>=18'} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2705,10 +2630,6 @@ packages: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} engines: {node: '>=0.1.90'} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -2768,10 +2689,6 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - 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'} @@ -2782,10 +2699,6 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} @@ -2975,10 +2888,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -3429,9 +3338,6 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} @@ -3455,10 +3361,6 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - ext-list@2.2.2: resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} engines: {node: '>=0.10.0'} @@ -3603,10 +3505,6 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - find-my-way@8.2.2: resolution: {integrity: sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==} engines: {node: '>=14'} @@ -3649,15 +3547,6 @@ packages: debug: optional: true - 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'} @@ -3670,10 +3559,6 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} - engines: {node: '>= 6'} - format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -3690,10 +3575,6 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -3965,19 +3846,12 @@ packages: html-element-attributes@2.3.0: resolution: {integrity: sha512-RJv2v3BBaYSc0ODHwT0sqWI+2lFs6DATBvCRnW20BDmULxoAWvfT6r28uL8LcW1a9/eqUl+1DccUOJzw00qVXQ==} - html-entities@1.4.0: - resolution: {integrity: sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==} - html-void-elements@2.0.1: resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - htmlparser@1.7.7: - resolution: {integrity: sha512-zpK66ifkT0fauyFh2Mulrq4AqGTucxGtOhZ8OjkbSfcCpkqQEI8qRkY0tSQSJNAQ4HUZkgWaU4fK4EH6SVH9PQ==} - engines: {node: '>=0.1.33'} - http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} @@ -3989,10 +3863,6 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - http-proxy-middleware@2.0.9: resolution: {integrity: sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==} engines: {node: '>=12.0.0'} @@ -4191,9 +4061,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true - is-electron@2.2.2: - resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} - is-error-instance@2.0.0: resolution: {integrity: sha512-5RuM+oFY0P5MRa1nXJo6IcTx9m2VyXYhRtb4h0olsi2GHci4bqZ6akHk+GmCYvDrAR9yInbiYdr2pnoqiOMw/Q==} engines: {node: '>=16.17.0'} @@ -4291,9 +4158,6 @@ 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'} @@ -4739,20 +4603,12 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - merge-options@3.0.4: resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} engines: {node: '>=10'} @@ -4937,10 +4793,6 @@ packages: 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'} - mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -5062,10 +4914,6 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -5309,10 +5157,6 @@ packages: resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} engines: {node: '>=18'} - 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'} @@ -5341,26 +5185,14 @@ packages: resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} engines: {node: '>=18'} - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - p-reduce@3.0.0: resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} engines: {node: '>=12'} - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} - p-retry@6.2.1: resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} engines: {node: '>=16.17'} - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - p-timeout@5.1.0: resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} engines: {node: '>=12'} @@ -5455,9 +5287,6 @@ packages: path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - path-type@6.0.0: resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} engines: {node: '>=18'} @@ -5648,10 +5477,6 @@ packages: 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'} - ps-list@8.1.1: resolution: {integrity: sha512-OPS9kEJYVmiO48u/B9qneqhkMvgCxT+Tm28VCEJpheTpl8cJ0ffZRRNgS5mrQRTrX5yRTpaJ+hRDeefXYmmorQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5681,10 +5506,6 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - qs@6.15.1: - resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} - engines: {node: '>=0.6'} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -5943,10 +5764,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - run-applescript@7.1.0: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} @@ -6053,18 +5870,10 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -6133,9 +5942,6 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - slackify-html@1.0.1: - resolution: {integrity: sha512-9e5Wo8Z2QSORedN6vqImnjIUwaHI8mpjeQQfXBcIcvIewoJ9SGB56MN2FVIPt6ACn+g4gLsQZHeGXwe5VQMnzA==} - slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -6218,10 +6024,6 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -6576,10 +6378,6 @@ 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'} @@ -6601,10 +6399,6 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -7974,10 +7768,6 @@ snapshots: - rollup - supports-color - '@netlify/functions@5.2.0': - dependencies: - '@netlify/types': 2.6.0 - '@netlify/git-utils@6.0.2': dependencies: execa: 8.0.1 @@ -8085,8 +7875,6 @@ snapshots: '@netlify/types@2.2.0': {} - '@netlify/types@2.6.0': {} - '@netlify/zip-it-and-ship-it@14.1.14': dependencies: '@babel/parser': 7.28.5 @@ -8512,70 +8300,6 @@ snapshots: dependencies: escape-string-regexp: 5.0.0 - '@slack/bolt@4.7.2': - dependencies: - '@slack/logger': 4.0.1 - '@slack/oauth': 3.0.5 - '@slack/socket-mode': 2.0.7 - '@slack/types': 2.21.0 - '@slack/web-api': 7.15.2 - axios: 1.16.0 - express: 5.2.1 - path-to-regexp: 8.4.2 - raw-body: 3.0.1 - tsscmp: 1.0.6 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - - '@slack/logger@4.0.1': - dependencies: - '@types/node': 24.12.4 - - '@slack/oauth@3.0.5': - dependencies: - '@slack/logger': 4.0.1 - '@slack/web-api': 7.15.2 - '@types/jsonwebtoken': 9.0.10 - '@types/node': 24.12.4 - jsonwebtoken: 9.0.2 - transitivePeerDependencies: - - debug - - '@slack/socket-mode@2.0.7': - dependencies: - '@slack/logger': 4.0.1 - '@slack/web-api': 7.15.2 - '@types/node': 24.12.4 - '@types/ws': 8.18.1 - eventemitter3: 5.0.4 - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - debug - - utf-8-validate - - '@slack/types@2.21.0': {} - - '@slack/web-api@7.15.2': - dependencies: - '@slack/logger': 4.0.1 - '@slack/types': 2.21.0 - '@types/node': 24.12.4 - '@types/retry': 0.12.0 - axios: 1.16.0 - eventemitter3: 5.0.4 - form-data: 4.0.5 - 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 - '@so-ric/colorspace@1.1.6': dependencies: color: 5.0.3 @@ -8649,11 +8373,6 @@ snapshots: '@types/json5@0.0.29': {} - '@types/jsonwebtoken@9.0.10': - dependencies: - '@types/ms': 2.1.0 - '@types/node': 24.12.4 - '@types/leaflet.markercluster@1.5.6': dependencies: '@types/leaflet': 1.9.21 @@ -8685,6 +8404,7 @@ snapshots: '@types/node@24.12.4': dependencies: undici-types: 7.16.0 + optional: true '@types/normalize-package-data@2.4.4': {} @@ -8700,16 +8420,12 @@ snapshots: '@types/require-dir@1.0.4': {} - '@types/retry@0.12.0': {} - '@types/retry@0.12.2': {} '@types/sanitize-html@2.16.0': dependencies: htmlparser2: 8.0.2 - '@types/slackify-html@1.0.8': {} - '@types/triple-beam@1.3.5': {} '@types/ungap__structured-clone@1.2.0': {} @@ -8718,10 +8434,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/ws@8.18.1': - dependencies: - '@types/node': 24.12.4 - '@types/yauzl@2.10.3': dependencies: '@types/node': 24.10.4 @@ -9217,11 +8929,6 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -9475,8 +9182,6 @@ snapshots: async@3.2.6: {} - asynckit@0.4.0: {} - atomic-sleep@1.0.0: {} atomically@2.1.0: @@ -9495,14 +9200,6 @@ snapshots: axe-core@4.11.4: {} - axios@1.16.0: - dependencies: - follow-redirects: 1.16.0 - form-data: 4.0.5 - proxy-from-env: 2.1.0 - transitivePeerDependencies: - - debug - axobject-query@4.1.0: {} b4a@1.7.3: {} @@ -9564,20 +9261,6 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.2.2: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.3(supports-color@5.5.0) - http-errors: 2.0.0 - iconv-lite: 0.7.1 - on-finished: 2.4.1 - qs: 6.15.1 - raw-body: 3.0.1 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - boolbase@1.0.0: {} bootstrap@4.6.2(jquery@3.7.1)(popper.js@1.16.1): @@ -9821,10 +9504,6 @@ snapshots: colors@1.4.0: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - comma-separated-tokens@2.0.3: {} commander@10.0.1: {} @@ -9884,16 +9563,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.1.0: {} - content-type@1.0.5: {} cookie-es@1.2.2: {} cookie-signature@1.0.6: {} - cookie-signature@1.2.2: {} - cookie@0.7.1: {} cookie@0.7.2: {} @@ -10071,8 +9746,6 @@ snapshots: defu@6.1.4: {} - delayed-stream@1.0.0: {} - depd@1.1.2: {} depd@2.0.0: {} @@ -10745,8 +10418,6 @@ snapshots: eventemitter3@4.0.7: {} - eventemitter3@5.0.4: {} - events-universal@1.0.1: dependencies: bare-events: 2.8.2 @@ -10819,39 +10490,6 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.2 - content-disposition: 1.1.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3(supports-color@5.5.0) - 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.0 - 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.1 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.1 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - ext-list@2.2.2: dependencies: mime-db: 1.54.0 @@ -11030,17 +10668,6 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.1: - dependencies: - debug: 4.4.3(supports-color@5.5.0) - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - find-my-way@8.2.2: dependencies: fast-deep-equal: 3.1.3 @@ -11082,8 +10709,6 @@ snapshots: optionalDependencies: debug: 4.4.3(supports-color@5.5.0) - follow-redirects@1.16.0: {} - for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -11095,14 +10720,6 @@ snapshots: form-data-encoder@2.1.4: {} - form-data@4.0.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - format@0.2.2: {} formdata-polyfill@4.0.10: @@ -11113,8 +10730,6 @@ snapshots: fresh@0.5.2: {} - fresh@2.0.0: {} - from2@2.3.0: dependencies: inherits: 2.0.4 @@ -11492,8 +11107,6 @@ snapshots: html-element-attributes@2.3.0: {} - html-entities@1.4.0: {} - html-void-elements@2.0.1: {} htmlparser2@8.0.2: @@ -11503,8 +11116,6 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 - htmlparser@1.7.7: {} - http-cache-semantics@4.2.0: {} http-errors@1.8.1: @@ -11523,14 +11134,6 @@ snapshots: statuses: 2.0.1 toidentifier: 1.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 - http-proxy-middleware@2.0.9(debug@4.4.3): dependencies: '@types/http-proxy': 1.17.17 @@ -11791,8 +11394,6 @@ snapshots: is-docker@3.0.0: {} - is-electron@2.2.2: {} - is-error-instance@2.0.0: {} is-extglob@2.1.1: {} @@ -11861,8 +11462,6 @@ snapshots: is-plain-object@5.0.0: {} - is-promise@4.0.0: {} - is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -12423,14 +12022,10 @@ snapshots: media-typer@0.3.0: {} - media-typer@1.1.0: {} - memoize-one@6.0.0: {} merge-descriptors@1.0.3: {} - merge-descriptors@2.0.0: {} - merge-options@3.0.4: dependencies: is-plain-obj: 2.1.0 @@ -12802,10 +12397,6 @@ snapshots: dependencies: mime-db: 1.52.0 - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - mime@1.6.0: {} mime@3.0.0: {} @@ -12906,8 +12497,6 @@ snapshots: negotiator@0.6.3: {} - negotiator@1.0.0: {} - neo-async@2.6.2: optional: true @@ -13300,8 +12889,6 @@ snapshots: dependencies: p-map: 7.0.3 - p-finally@1.0.0: {} - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -13328,28 +12915,14 @@ snapshots: p-map@7.0.3: {} - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - p-reduce@3.0.0: {} - p-retry@4.6.2: - dependencies: - '@types/retry': 0.12.0 - retry: 0.13.1 - p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 is-network-error: 1.3.0 retry: 0.13.1 - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - p-timeout@5.1.0: {} p-timeout@6.1.4: {} @@ -13433,8 +13006,6 @@ snapshots: path-to-regexp@0.1.12: {} - path-to-regexp@8.4.2: {} - path-type@6.0.0: {} pathe@1.1.2: {} @@ -13628,8 +13199,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-from-env@2.1.0: {} - ps-list@8.1.1: {} pstree.remy@1.1.8: {} @@ -13656,10 +13225,6 @@ snapshots: dependencies: side-channel: 1.1.0 - qs@6.15.1: - dependencies: - side-channel: 1.1.0 - queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -14012,16 +13577,6 @@ snapshots: rfdc@1.4.1: {} - router@2.2.0: - dependencies: - debug: 4.4.3(supports-color@5.5.0) - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color - run-applescript@7.1.0: {} run-async@2.4.1: {} @@ -14138,22 +13693,6 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.2.1: - dependencies: - debug: 4.4.3(supports-color@5.5.0) - 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.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -14163,15 +13702,6 @@ snapshots: 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-cookie-parser@2.7.2: {} @@ -14279,11 +13809,6 @@ snapshots: dependencies: semver: 7.7.3 - slackify-html@1.0.1: - dependencies: - html-entities: 1.4.0 - htmlparser: 1.7.7 - slash@5.1.0: {} slashes@3.0.12: {} @@ -14354,8 +13879,6 @@ snapshots: statuses@2.0.1: {} - statuses@2.0.2: {} - std-env@3.10.0: {} stop-iteration-iterator@1.1.0: @@ -14741,8 +14264,6 @@ snapshots: tslib@2.8.1: {} - tsscmp@1.0.6: {} - tsx@4.21.0: dependencies: esbuild: 0.27.2 @@ -14763,12 +14284,6 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.2 - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 diff --git a/scripts/build-rooms.ts b/scripts/build-rooms.ts deleted file mode 100644 index 86405d804..000000000 --- a/scripts/build-rooms.ts +++ /dev/null @@ -1,33 +0,0 @@ -import 'dotenv/config'; -import Airtable from 'airtable'; -import { writeFileSync, mkdirSync } from 'node:fs'; -import path from 'node:path'; - -const outDir = path.join('.', 'data'); -const outFile = path.join(outDir, 'rooms.json'); - -async function main() { - mkdirSync(outDir, { recursive: true }); - - if (!process.env.AIRTABLE_COWORKING_BASE) { - console.warn( - '[build-rooms] AIRTABLE_COWORKING_BASE not set — writing empty rooms.json. The zoom-meeting-webhook-handler will not match any meetings until this is configured.', - ); - writeFileSync(outFile, '[]\n'); - return; - } - - console.log('Building rooms'); - const base = new Airtable().base(process.env.AIRTABLE_COWORKING_BASE); - const results = await base('rooms').select().all(); - - const rooms = results.map((record) => ({ - ...record.fields, - record_id: record.id, - })); - - writeFileSync(outFile, JSON.stringify(rooms, null, 2)); - console.log(`Done building ${rooms.length} rooms`); -} - -main(); From f093858d9059f6fd4e7f9fd3b381279ea89a2dda Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:24:02 -0400 Subject: [PATCH 2/2] Add /bots/* rewrite to vc-bots Cloudflare Worker Proxies /bots/* to the worker that replaced the removed webhook functions, stripping the /bots prefix via :splat. --- netlify.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netlify.toml b/netlify.toml index 0cd8ba34b..9cbbb52f6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -208,6 +208,12 @@ to = "/.netlify/functions/join-slack" status = 200 +[[redirects]] + from = "/bots/*" + to = "https://vc-bots.lucky-art-2f02.workers.dev/:splat" + status = 200 + force = true + [[redirects]] from = "/plausible/js/script.js" to = "https://plausible.io/js/script.js"