|
|
||
|---|---|---|
| .. | ||
| src | ||
| .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.