Janet is now a split Slack karma platform with three main binaries:
janet-bot: listens to Slack events in Socket Mode and records live karma activity.janet-server: serves the analytics UI and JSON API from PostgreSQL-backed summary data.janet-backfill: imports historical Slack messages and reaction data into the database.
This branch replaces the older single-process bot/web setup with a database-first architecture aimed at long-lived analytics, historical backfills, and a richer web dashboard.
- Separates live bot traffic from the web UI and analytics server.
- Moves the app to PostgreSQL with a unified
karma_transactionstable plus cached summary tables. - Adds a React dashboard with leaderboard, overview, analytics, activity, users, and popular-message views.
- Adds a backfill CLI for importing Slack history and emoji reactions.
- Adds cached Slack message enrichment for popular posts, including permalinks, author metadata, and attachment/image URLs.
- Adds Docker Compose stacks for local and production-style deployment.
- Adds a small maintenance command for filling missing
channel_idvalues.
cmd/janet-bot: live Slack bot service.janet-server: HTTP server for the dashboard and API.cmd/janet-backfill: ad hoc or bulk history import tool.cmd/janet-maintenance: one-off database maintenance tasks.
janet-botreceives Slack message and reaction events through Socket Mode.- Karma transactions are written to PostgreSQL.
janet-serverrebuilds summary tables on startup and serves analytics from those summaries.janet-backfillcan import older channel history and emoji reactions into the same database.- The popular-messages API lazily enriches records with Slack metadata and caches the result.
The bot still supports Janet-style karma commands:
- Upvote:
alice++ - Downvote:
alice-- - Multi-point vote:
alice++++ - Reason text:
alice++ for fixing the deploy - Query points:
karma for alice - Throwback:
goodplace throwback alice - Leaderboard:
goodplace leaderboard - Larger leaderboard:
goodplace top 20 - Year-specific leaderboard in chat:
goodplace highscores 20 2025 - Motivate syntax:
alice motivate
Slack mentions are also supported, for example:
<@U12345>++@alice ++
Reaction-based karma is enabled as well. The bot treats configured positive reactions such as :+1:, :thumbsup:, :joy:, :heart:, and others as positive awards, and :-1: / :thumbsdown: as negative awards.
The web app is a single-page dashboard with these sections:
- Overview: totals and high-level trend cards.
- Leaderboard: yearly or all-time ranking plus top givers.
- Popular: high-reaction messages with Slack enrichment and optional media filtering.
- Analytics: emoji usage, distribution, and time-based charts.
- Activity: paginated recent transaction feed with filtering.
- Users: per-user summaries and points-over-time views.
The backfill command can:
- List accessible Slack channels.
- Import a specific time window from a channel.
- Optionally include emoji reactions.
- Run as a dry run before writing data.
- Limit the number of fetched messages for smaller test imports.
cmd/janet-bot/ Live Slack bot
cmd/janet-backfill/ Historical Slack importer
cmd/janet-maintenance/ Database maintenance utility
database/ PostgreSQL access layer, migrations, summaries
janet-server/ Web server, handlers, templates, embedded assets
janet-server/web-react/ React source for the dashboard
docker-compose.yml Local multi-service stack
docker-compose.production.yml Production-style stack
- Go 1.25+
- Node 18+ if you want to rebuild the React app
- PostgreSQL 17+
- Slack bot token and app-level Socket Mode token
- Optional Slack user token for richer web enrichment and backfill behavior
- Docker and Docker Compose for containerized setup
Copy .env.example to .env and fill in the values you actually use.
JANET_SLACK_TOKEN: bot token (xoxb-...)JANET_SLACK_SOCKET_TOKEN: app-level Socket Mode token (xapp-...)JANET_GOOD_PLACE_JUDGE_BOT_ID: Slack bot user IDJANET_DATABASE_URL: PostgreSQL connection string
JANET_DATABASE_URLJANET_WEB_LISTEN_ADDRJANET_BOT_ENABLED=falseif running the server in web-only mode
JANET_WEB_TOKEN
JANET_WEB_TOKEN can be:
- A user token (
xoxp-...) withsearch:read,channels:history, andgroups:historyfor best results. - A bot token (
xoxb-...) with history scopes, if you only need enrichment where channel IDs are already known.
JANET_MAX_POINTSJANET_LEADERBOARD_LIMITJANET_REPLY_TYPEJANET_DEBUGJANET_SELF_KARMAJANET_MOTIVATEJANET_REACTJI_ENABLEDJANET_GOOD_USERNAMEJANET_GOOD_ICON_URLJANET_BAD_USERNAMEJANET_BAD_ICON_URL
JANET_ATTACHMENTS_DIRJANET_WEB_PUBLIC_URLJANET_WEB_RATE_LIMIT_RPSJANET_WEB_RATE_LIMIT_BURSTJANET_RUN_CHANNELID_BACKFILL
The Compose stacks front janet-web with oauth2-proxy. Configure:
OAUTH2_PROXY_CLIENT_IDOAUTH2_PROXY_CLIENT_SECRETOAUTH2_PROXY_COOKIE_SECRETOAUTH2_PROXY_REDIRECT_URLOAUTH2_PROXY_EMAIL_DOMAINS
cp .env.example .env
docker compose up --buildThis starts:
postgresjanet-botjanet-weboauth2-proxyjanet-backfillas an interactive container entrypoint
The public entrypoint is http://localhost:8080, which hits oauth2-proxy, then forwards to janet-web.
Start the stack, then run the backfill container with flags:
docker compose run --rm janet-backfill -list-channels
docker compose run --rm janet-backfill -channel C0123456789 -since 2025-01-01 -until 2025-12-31 -include-emoji
docker compose run --rm janet-backfill -channel C0123456789 -since 2025-01-01 -dry-rundocker compose -f docker-compose.production.yml up -dThat stack expects prebuilt images such as troyxmccall/janet:bot and troyxmccall/janet:web.
Create a database and apply database/migration.sql.
export JANET_DATABASE_URL='postgres://janet:password@localhost:5432/janet?sslmode=disable'
export JANET_SLACK_TOKEN='xoxb-...'
export JANET_SLACK_SOCKET_TOKEN='xapp-...'
export JANET_GOOD_PLACE_JUDGE_BOT_ID='U0123456789'
go run ./cmd/janet-botexport JANET_DATABASE_URL='postgres://janet:password@localhost:5432/janet?sslmode=disable'
export JANET_WEB_LISTEN_ADDR=':8080'
export JANET_BOT_ENABLED='false'
export JANET_WEB_TOKEN='xoxp-...'
go run ./janet-serverexport JANET_DATABASE_URL='postgres://janet:password@localhost:5432/janet?sslmode=disable'
export JANET_SLACK_TOKEN='xoxb-...'
go run ./cmd/janet-backfill -list-channels
go run ./cmd/janet-backfill -channel C0123456789 -since 2025-01-01 -until 2025-03-31 -include-emojiexport JANET_DATABASE_URL='postgres://janet:password@localhost:5432/janet?sslmode=disable'
go run ./cmd/janet-maintenance -fill-channel-idsThe deployed server embeds static assets from janet-server/web/static, and the Docker build regenerates those assets from the React app in janet-server/web-react.
If you are actively editing the dashboard UI:
cd janet-server/web-react
npm ci
npm run devTo build the static bundle:
cd janet-server/web-react
npm ci
npm run buildAll API routes are served from /api.
GET /api/leaderboard?limit=25GET /api/leaderboard/currentGET /api/leaderboard/2025?limit=100
Example:
curl 'http://localhost:8080/api/leaderboard/2025?limit=10'GET /api/statsGET /api/stats?year=2025GET /api/stats/detailed?year=2025GET /api/stats/years
Example:
curl 'http://localhost:8080/api/stats/detailed?year=2025'GET /api/stats/top-givers?limit=10GET /api/stats/top-givers/2025?limit=10GET /api/stats/emojis?year=2025&limit=15GET /api/stats/karma-distribution?year=2025
GET /api/stats/activity-timeline?year=2025GET /api/stats/points-over-time?year=2025GET /api/stats/recent-activity?limit=50&offset=0&from=alice&to=bob&year=2025
Example:
curl 'http://localhost:8080/api/stats/recent-activity?limit=20&from=alice&year=2025'GET /api/stats/popular-messages?year=2025GET /api/stats/popular-messages?year=2025&user=aliceGET /api/stats/popular-messages?year=2025&min_reactions=5&has_media=1GET /api/stats/popular-messages?year=2025&funny_bias=1&include_meta=1
Example:
curl 'http://localhost:8080/api/stats/popular-messages?year=2025&min_reactions=8&has_media=1'GET /api/user/aliceGET /api/user/alice/2025GET /api/user/alice/2025/points-over-timeGET /api/user/alice/points-over-time/all
Example:
curl 'http://localhost:8080/api/user/alice/2025'GET /api/status
- Copy
.env.exampleto.env. - Fill in Slack tokens and OAuth settings.
- Run
docker compose up --build. - Visit
http://localhost:8080. - Run
docker compose run --rm janet-backfill -list-channels. - Backfill the channels you care about.
go run ./cmd/janet-backfill \
-channel C0123456789 \
-since 2025-01-01 \
-until 2025-12-31 \
-include-emojigo run ./cmd/janet-backfill \
-channel C0123456789 \
-since 2025-01-01 \
-limit 100 \
-dry-runcurl 'http://localhost:8080/api/leaderboard/2025?limit=25'
curl 'http://localhost:8080/api/stats/top-givers/2025?limit=10'
curl 'http://localhost:8080/api/stats/popular-messages?year=2025&min_reactions=10'janet-serverrebuilds summary tables on startup to keep read-heavy endpoints fast.janet-serveralso attempts to reset database sequences on startup after imports.- Popular-message enrichment is cached in
popular_message_cacheto reduce Slack API calls. - Attachments can be cached to
JANET_ATTACHMENTS_DIRand served from/attachments/.... - API responses filter out bots and deleted Slack users by default unless
show_all=trueis used where supported.
- The old single
cmd/janetbinary is gone. - The old
janetctlCLI is gone. - The old server-rendered web UI under
www/has been replaced byjanet-serverplus a React SPA. - PostgreSQL is now the primary storage model in the checked-in Compose setup.