Skip to content

Repository files navigation

Catalyst

Slack emoji reaction leaderboard.

Built as a Bun workspaces monorepo:

PackageDescriptionDeployed to
packages/workerHono API + Slack event handler (CF Worker)catalyst.scstem.tech/api/*
packages/webReact frontend (CF Pages)catalyst.scstem.tech

Stack: Cloudflare Workers + D1, Hono, slack-cloudflare-workers, React 19, TanStack Router + Query, TailwindCSS v4

Prerequisites

Quick start (local dev)

# 1. Install dependencies
bun install
# 2. Create your local secrets file
cp mise.local.toml.example mise.local.toml
# 3. Create + migrate the local D1 database
mise run db:migrate
# 4. Run the worker + web dev servers
mise run dev
# 5. Open the site. The AccessGate takes password **`000000`** by default.

The UI will look empty until you've imported some Slack data — see Backfill historical data below.

Environment variables

VariableWhyDefault
SITE_PASSWORDAccessGate password prompt000000 (in mise.toml)
TURNSTILE_SECRET_KEYValidates captcha token server-sideCF test secret (in mise.toml)
VITE_TURNSTILE_SITE_KEYFrontend captcha widgetFrontend falls back to CF test site key
SLACK_BOT_TOKENBackfill script, /api/slack/events, /backfill workflow
SLACK_SIGNING_SECRET/api/slack/events signature verification
CLOUDFLARE_API_TOKENmise run deploy:* and remote db:migrate/backfill

Set up a Slack app

Skip this section if you're only doing frontend or read-API work — the AccessGate, charts, and rankings all work without Slack once you've got data in D1.

  1. Go to api.slack.com/appsCreate New AppFrom scratch
  2. Name it whatever you want and pick your workspace

Bot token scopes

Under OAuth & Permissions, add these Bot Token Scopes:

ScopeUsed for
reactions:readReceiving reaction events
chat:writeBot messages (backfill status, slash command responses)
commandsSlash commands (/catalyst, /backfill)
channels:historyBackfill: reading message history
groups:historyBackfill: private channel history
channels:readBackfill: listing channels
groups:readBackfill: listing private channels
users:readFetching user profiles (display names + avatars)
emoji:readFetching custom emoji images

Event subscriptions

Under Event Subscriptions:

  1. Toggle Enable Events on
  2. Set the Request URL to your deployed worker (e.g. https://catalyst.scstem.tech/api/slack/events)
  3. Subscribe to bot events: reaction_added, reaction_removed

Slash commands

Create two commands, both pointing at /api/slack/events:

CommandDescription
/catalystPing the Catalyst bot
/backfillBackfill emoji reactions for this channel

The shared endpoint is fine — the slack-cloudflare-workers SDK dispatches by payload type.

Install + grab credentials

  • OAuth & PermissionsInstall to Workspace → copy the Bot User OAuth Token (xoxb-…)
  • Basic InformationApp Credentials → copy the Signing Secret

Put both in mise.local.toml as SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET.

Backfill historical data

The backfill task pulls all existing reactions from Slack into D1 and rebuilds the aggregate tables.

mise run backfill # local D1 (default)
mise run backfill staging # remote: staging D1
mise run backfill prod # remote: production D1

Requires SLACK_BOT_TOKEN in mise.local.toml. Remote targets also need CLOUDFLARE_API_TOKEN and a clean git working directory.

Optional cutoff: BACKFILL_SINCE=2025-01-01 mise run backfill (ISO date).

The bot must be invited to any channels you want to backfill (/invite @YourBotName).

For on-demand single-channel backfills, use the /backfill slash command inside Slack — it triggers a Cloudflare Workflow that pages through the channel's history with automatic retries.

Pull remote data into local D1

To work against real data locally, pull a snapshot of a remote D1 into your local one:

mise run db:pull # from staging (default)
mise run db:pull prod # from production (requires confirmation)

This wipes the local D1 state, imports the remote dump (schema + data), then re-applies migrations so any unreleased schema changes are reapplied on top. Requires CLOUDFLARE_API_TOKEN.

Deploying

See DEPLOY.md for:

  • First-time Cloudflare setup (D1 databases, Pages project, production secrets, environment variables)
  • Day-to-day deploy commands and safety guardrails
  • Infrastructure overview

Production secrets are managed via wrangler secret put — separately from your local mise.local.toml.

Architecture

┌──────────────────┐ POST /api/slack/events ┌─────────────────────┐
│ Slack Events │ ──────────────────────────► │ slack-cloudflare- │
│ API │ │ workers SDK │
└──────────────────┘ │ (sig verification, │
│ challenge, parse) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ addReaction() / │
│ removeReaction() │
│ → D1 batch write │
└─────────────────────┘
┌──────────────────┐ GET /api/* ┌─────────────────────┐
│ React frontend │ ──────────────────────────► │ Hono app │
│ (hono/client) │ ◄────────────────────────── │ → Drizzle → D1 │
└──────────────────┘ JSON (type-safe RPC) └─────────────────────┘

About

Slack bot (and more) that gets the whole team moving!

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages