|
Some checks failed
CI / test (pull_request) Has been cancelled
Enriches webhook logs with sender and ref for better visibility into who triggered events and what they affected (e.g. which branch). PR logs now include the title, comment logs include the first 200 characters of the comment body. Adds an expandable detail row in the log viewer UI that shows all metadata as key-value pairs when clicked, keeping the summary line clean. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Spike
Discord-Gitea bridge bot. When someone opens a PR or leaves a comment on git.nose.space, Spike posts it to a Discord channel. When someone replies in the Discord thread, Spike posts it back to Gitea.
Setup
- Install bun and clone this repo.
bun installfrom the repo root.cp .env.example .envand fill in the values (see below).bun run subdomain:devto start the server.- Visit
localhost:3000/discord/authto authorize the bot to your Discord server. - Use Tailscale Funnel or ngrok to expose your local server to the internet.
- Add a Gitea webhook at
https://git.nose.space/<org>/settings/hookspointing tohttps://<your-tunnel>/gitea/webhook.
Environment variables
Copy .env.example and fill in:
| Variable | What it is |
|---|---|
DISCORD_TOKEN |
Bot token from Discord Developer Portal |
DISCORD_CLIENT_ID |
Application ID from the same portal |
GITEA_API_TOKEN |
Personal access token from git.nose.space (Settings > Applications) |
PORT |
Server port (default 3000) |
NODE_ENV |
Set to production for prod, leave blank for dev |
DATA_DIR |
Where to store the SQLite DB in prod |
For running integration tests, you also need TEST_GITEA_API_TOKEN_COREY, TEST_GITEA_API_TOKEN_SPIKE, TEST_REPO_COREY, and TEST_REPO_SPIKE — see .env.example for defaults.
Username mappings
Spike converts between Gitea and Discord usernames so @mentions work across platforms. These mappings live in src/config.ts under giteaToDiscordUserMappings. Add your Gitea username → Discord username pair there.
Architecture
src/
├── server.tsx — HTTP server (webhook endpoint, error log)
├── config.ts — Dev/prod config (DB paths, channel IDs, username mappings)
├── gitea/ — Pure Gitea API client and types
├── discord/ — Discord bot client, events, slash commands
└── bridge/ — Wiring between Gitea and Discord (webhook handler, DB, helpers)
Dependencies flow one way: bridge/ → gitea/ and bridge/ → discord/. The gitea and discord modules don't know about each other — bridge is the only thing that connects them.
How the code is organized
Each directory with an index.ts is a standalone lib. The index.ts is a barrel — it re-exports the lib's public API. Everything else inside the directory is internal.
The rule: external code imports from the barrel only. Never reach into a lib's internal files.
// Good
import { handleGiteaWebhook } from "./bridge"
// Bad — reaches into internals
import { handleGiteaWebhook } from "./bridge/webhook-handler"
Each lib has a README.md that documents its barrel exports. This is the contract — if you want to know what a lib does, read its README. You don't need to read every internal file.
This matters for AI-assisted development: when Claude works on code that uses a lib, it only needs the README to understand the API. When it works on code inside a lib, it reads the internals. This keeps context small and focused.
The three libs
gitea/ — Pure Gitea API. Fetches PRs and review comments, converts usernames, formats thread names. No side effects, no Discord, no database. Unit-testable in ~15ms.
discord/ — Discord bot setup. Creates and logs in the client, registers slash commands, listens for messages. When someone types in a PR thread, it hands off to bridge to relay the message to Gitea.
bridge/ — The glue. Receives Gitea webhooks and creates Discord threads/messages. Receives Discord messages and posts Gitea comments. Owns the SQLite database that maps Gitea PR IDs ↔ Discord thread IDs and Gitea comment IDs ↔ Discord message IDs.
Data flow
Gitea webhook POST → server.tsx → bridge/handleGiteaWebhook → Discord thread/message
Discord message → discord/events → bridge/createPRComment → Gitea comment
Running
bun run subdomain:dev— Start with hot reloadbun run subdomain:start— Production modebun test— Run integration tests (requires Tailscale funnel + test env vars)
Tests
Unit tests for pure functions (username conversion, thread naming) run in ~15ms with no external dependencies.
Integration tests for webhooks require a live Gitea instance and Tailscale funnel. They open real PRs, post real comments, and verify the correct webhooks arrive. See src/gitea/test/ for details.