# Spike

Discord-Gitea bridge bot. When someone opens a PR or leaves a comment on [git.nose.space](https://git.nose.space), Spike posts it to a Discord channel. When someone replies in the Discord thread, Spike posts it back to Gitea.

## Setup

1. Install [bun](https://bun.sh) and clone this repo.
2. `bun install` from the repo root.
3. `cp .env.example .env` and fill in the values (see below).
4. `bun run subdomain:dev` to start the server.
5. Visit `localhost:3000/discord/auth` to authorize the bot to your Discord server.
6. Use [Tailscale Funnel](https://tailscale.com/kb/1223/funnel) or ngrok to expose your local server to the internet.
7. Add a Gitea webhook at `https://git.nose.space/<org>/settings/hooks` pointing to `https://<your-tunnel>/gitea/webhook`.

### Environment variables

Copy `.env.example` and fill in:

| Variable | What it is |
|---|---|
| `DISCORD_TOKEN` | Bot token from [Discord Developer Portal](https://discord.com/developers/applications) |
| `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.

```ts
// 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/](src/gitea/README.md)** — 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/](src/discord/README.md)** — 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/](src/bridge/README.md)** — 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 reload
- `bun run subdomain:start` — Production mode
- `bun 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.
