mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
docs: document admin groups, OAuth redirect, and API renames
This commit is contained in:
parent
8c8cfbf211
commit
8c9720b1b3
10 changed files with 324 additions and 142 deletions
|
|
@ -57,16 +57,16 @@ Merges a pull request.
|
||||||
"owner": "org",
|
"owner": "org",
|
||||||
"repo": "repo",
|
"repo": "repo",
|
||||||
"pullNumber": 42,
|
"pullNumber": 42,
|
||||||
"mergeMethod": "squash"
|
"method": "squash"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Field | Type | Required | Description |
|
| Field | Type | Required | Description |
|
||||||
| ------------- | ------ | -------- | ------------------------------------------------- |
|
| ------------ | ------ | -------- | -------------------------------------------------- |
|
||||||
| `owner` | string | Yes | Repository owner |
|
| `owner` | string | Yes | Repository owner |
|
||||||
| `repo` | string | Yes | Repository name |
|
| `repo` | string | Yes | Repository name |
|
||||||
| `pullNumber` | number | Yes | Pull request number |
|
| `pullNumber` | number | Yes | Pull request number |
|
||||||
| `mergeMethod` | string | No | `merge`, `squash`, or `rebase` (default: `merge`) |
|
| `method` | string | No | `merge`, `squash`, or `rebase` (default: `squash`) |
|
||||||
|
|
||||||
**Response:** `200` with GitHub merge response.
|
**Response:** `200` with GitHub merge response.
|
||||||
|
|
||||||
|
|
@ -88,11 +88,11 @@ Closes a pull request without merging.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Field | Type | Required | Description |
|
| Field | Type | Required | Description |
|
||||||
| ------------ | ------ | -------- | ----------------------- |
|
| ------------ | ------ | -------- | ------------------- |
|
||||||
| `owner` | string | Yes | Repository owner |
|
| `owner` | string | Yes | Repository owner |
|
||||||
| `repo` | string | Yes | Repository name |
|
| `repo` | string | Yes | Repository name |
|
||||||
| `pullNumber` | number | Yes | Pull request number |
|
| `pullNumber` | number | Yes | Pull request number |
|
||||||
|
|
||||||
**Response:** `200` with GitHub update response.
|
**Response:** `200` with GitHub update response.
|
||||||
|
|
||||||
|
|
@ -111,7 +111,7 @@ Adds an emoji reaction to an issue or comment.
|
||||||
"owner": "org",
|
"owner": "org",
|
||||||
"repo": "repo",
|
"repo": "repo",
|
||||||
"issueNumber": 42,
|
"issueNumber": 42,
|
||||||
"content": "rocket"
|
"reaction": "rocket"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ Adds an emoji reaction to an issue or comment.
|
||||||
| `owner` | string | Yes | Repository owner |
|
| `owner` | string | Yes | Repository owner |
|
||||||
| `repo` | string | Yes | Repository name |
|
| `repo` | string | Yes | Repository name |
|
||||||
| `issueNumber` | number | Yes | Issue, PR, or comment number |
|
| `issueNumber` | number | Yes | Issue, PR, or comment number |
|
||||||
| `content` | string | Yes | Reaction type (see below) |
|
| `reaction` | string | Yes | Reaction type (see below) |
|
||||||
|
|
||||||
**Reaction Types:**
|
**Reaction Types:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ Redirects the user to GitHub's authorization page.
|
||||||
|
|
||||||
**Query Parameters:**
|
**Query Parameters:**
|
||||||
|
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
| --------- | ---------------------------------- |
|
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `userId` | Your application's user identifier |
|
| `redirect` | Optional relative path to return to after sign-in (e.g. `/admin`). Must start with `/` but not `//`; any unsafe value falls back to `/`. |
|
||||||
|
|
||||||
**Response:** `302` redirect to GitHub OAuth authorize URL.
|
**Response:** `302` redirect to GitHub OAuth authorize URL.
|
||||||
|
|
||||||
|
|
@ -42,7 +42,11 @@ GitHub redirects here after authorization. Exchanges the code for an access toke
|
||||||
| `code` | Authorization code |
|
| `code` | Authorization code |
|
||||||
| `state` | State parameter for CSRF protection |
|
| `state` | State parameter for CSRF protection |
|
||||||
|
|
||||||
**Response:** Redirects to your `BASE_URL` with a success/error indicator.
|
**Response:**
|
||||||
|
|
||||||
|
- **Browser flow** (`Accept: text/html`): sets an admin session cookie, then redirects to the `redirect` target. Users without admin access are redirected to `/admin?error=forbidden`.
|
||||||
|
- **JSON flow**: returns `{ "userId": "...", "login": "...", "redirectTo": "..." }`.
|
||||||
|
- **Discord link flow** (started with a pending `discordUserId`): links the Discord user to this GitHub account, returning `{ "ok": true, "discordUserId": "...", "login": "..." }` — or a success page in the browser.
|
||||||
|
|
||||||
### Revoke Token
|
### Revoke Token
|
||||||
|
|
||||||
|
|
@ -66,12 +70,14 @@ Tokens are stored in KV with key pattern `token:{userId}`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"userId": "12345",
|
||||||
"accessToken": "gho_...",
|
"accessToken": "gho_...",
|
||||||
"expiresAt": "2025-01-01T00:00:00.000Z"
|
"expiresAt": 1735689600000,
|
||||||
|
"refreshToken": "..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Tokens are automatically expired based on the `expiresAt` timestamp.
|
`expiresAt` is a Unix timestamp in milliseconds. KV entries expire at 90% of the token's lifetime (minimum 60 seconds). A reverse index `token-reverse:{sha256 of token}` maps the access token back to its user id so Bearer-authenticated endpoints can resolve the caller. Discord users linked to a GitHub account are stored under `discord-link:{discordUserId}`.
|
||||||
|
|
||||||
## Using Tokens
|
## Using Tokens
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,28 +10,34 @@ https://your-worker.workers.dev
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
| Method | Path | Auth | Description |
|
| Method | Path | Auth | Description |
|
||||||
| -------- | ----------------------- | -------------- | ------------------------ |
|
| -------- | ------------------------------ | -------------- | ------------------------ |
|
||||||
| `GET` | `/health` | None | Health check |
|
| `GET` | `/health` | None | Health check |
|
||||||
| `POST` | `/webhook` | HMAC signature | GitHub webhook ingestion |
|
| `POST` | `/webhook` | HMAC signature | GitHub webhook ingestion |
|
||||||
| `GET` | `/auth/github` | None | Start GitHub OAuth flow |
|
| `GET` | `/auth/github` | None | Start GitHub OAuth flow |
|
||||||
| `GET` | `/auth/github/callback` | None | OAuth callback |
|
| `GET` | `/auth/github/callback` | None | OAuth callback |
|
||||||
| `DELETE` | `/auth/token/:userId` | None | Revoke user token |
|
| `DELETE` | `/auth/token/:userId` | None | Revoke user token |
|
||||||
| `POST` | `/api/comment` | Bearer token | Create issue comment |
|
| `POST` | `/api/comment` | Bearer token | Create issue comment |
|
||||||
| `POST` | `/api/merge` | Bearer token | Merge pull request |
|
| `POST` | `/api/merge` | Bearer token | Merge pull request |
|
||||||
| `POST` | `/api/close` | Bearer token | Close pull request |
|
| `POST` | `/api/close` | Bearer token | Close pull request |
|
||||||
| `POST` | `/api/react` | Bearer token | Add reaction to issue |
|
| `POST` | `/api/react` | Bearer token | Add reaction to issue |
|
||||||
| `GET` | `/admin` | Admin session | Config console UI |
|
| `GET` | `/admin` | Admin session | Config console UI |
|
||||||
| `GET` | `/admin/api/routes` | Admin session | List routes |
|
| `GET` | `/admin/api/routes` | Admin session | List routes |
|
||||||
| `PUT` | `/admin/api/routes` | Admin session | Replace routes |
|
| `PUT` | `/admin/api/routes` | Admin session | Replace routes |
|
||||||
|
| `GET` | `/admin/api/groups` | Admin session | List groups (scoped) |
|
||||||
|
| `PUT` | `/admin/api/groups` | Admin session | Replace groups (super) |
|
||||||
|
| `GET` | `/admin/api/groups/:id/routes` | Admin session | List a group's routes |
|
||||||
|
| `PUT` | `/admin/api/groups/:id/routes` | Admin session | Replace a group's routes |
|
||||||
|
| `GET` | `/admin/api/me` | Admin session | Current session info |
|
||||||
|
| `GET` | `/admin/api/logs` | Admin session | Send logs (scoped) |
|
||||||
|
|
||||||
## Admin Console
|
## Admin Console
|
||||||
|
|
||||||
See [Configuration → Web UI](../guide/configuration.md#web-ui) for setup. Admin endpoints require a session cookie obtained via `GET /admin/login` (GitHub OAuth); the signed-in user must be listed in `ADMIN_USER_IDS`.
|
See [Configuration → Web UI](../guide/configuration.md#web-ui) for setup. Admin endpoints require a session cookie obtained via `GET /admin/login` (GitHub OAuth); the signed-in user must be listed in `ADMIN_USER_IDS` or manage a group.
|
||||||
|
|
||||||
- `GET /admin` — Serves the config console HTML
|
- `GET /admin` — Serves the config console HTML
|
||||||
- `GET /admin/api/routes` — Returns `{ "routes": Route[] }`
|
- `GET /admin/api/routes` — Returns `{ "routes": Route[] }`
|
||||||
- `PUT /admin/api/routes` — Body `{ "routes": Route[] }`; validates each route (id pattern, unique id, name, enabled, ≥1 valid filter, string `target.channelId`) and persists to KV `config:routes`. Returns `200 { ok, count }` or `400 { error }` / `401 { error }`.
|
- `PUT /admin/api/routes` — Body `{ "routes": Route[] }`; validates each route (id pattern, unique id, name, enabled, `groupId`, filters — empty only allowed for `fallback` routes — and string `target.channelId`) and persists to KV `config:routes`. Returns `200 { ok, count }` or `400 { error }` / `401 { error }`.
|
||||||
|
|
||||||
## Health Check
|
## Health Check
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,24 @@ npm run dev # Start local dev server
|
||||||
src/
|
src/
|
||||||
├── index.ts # CF Workers entry (fetch + scheduled), exports DiscordGateway DO
|
├── index.ts # CF Workers entry (fetch + scheduled), exports DiscordGateway DO
|
||||||
├── types.ts # Env, Config, Route, Filter, WebhookEvent, FormattedMessage
|
├── types.ts # Env, Config, Route, Filter, WebhookEvent, FormattedMessage
|
||||||
├── config.ts # Loads routes from KV (fallback to 7 defaults), builds Config from env
|
├── config.ts # Loads routes from KV (returns [] if unset), builds Config from env
|
||||||
├── server.ts # Hono app: /health, /webhook, mounts /auth + /
|
├── server.ts # Hono app: /health, /webhook, mounts /auth, /admin + /
|
||||||
├── webhook.ts # HMAC verify (Web Crypto), parseEvent, extractBranch, matchRoute
|
├── webhook.ts # HMAC verify (Web Crypto), parseEvent, extractBranch, matchRoute
|
||||||
├── discord.ts # Dispatch via DO RPC, initGateway (scheduled)
|
├── discord.ts # Dispatch via Discord REST (DO RPC when gateway enabled), initGateway (scheduled)
|
||||||
|
├── discord-rest.ts # Discord REST sendMessage with retry + rate-limit handling
|
||||||
├── discord-gateway.ts # Durable Object: Discord Gateway WS, heartbeat, channel cache, send
|
├── discord-gateway.ts # Durable Object: Discord Gateway WS, heartbeat, channel cache, send
|
||||||
├── formatter.ts # 23 event formatters + generic fallback
|
├── formatter.ts # 23 event formatters + generic fallback
|
||||||
├── github-oauth.ts # OAuth URL, callback token exchange, getUserOctokit
|
├── github-oauth.ts # OAuth URL, callback token exchange, getUserOctokit
|
||||||
├── oauth-routes.ts # GET /auth/github, callback, DELETE /token/:userId (KV state)
|
├── oauth-routes.ts # GET /auth/github, callback, DELETE /token/:userId (KV state)
|
||||||
├── action-routes.ts # POST /api/comment|merge|react (Bearer token auth via KV lookup)
|
├── action-routes.ts # POST /api/comment|merge|close|react (Bearer token auth via KV lookup)
|
||||||
|
├── admin-routes.ts # /admin API: routes, groups, me, logs (session + scope auth)
|
||||||
|
├── admin-session.ts # Admin session CRUD (KV session:{id}), cookie helpers
|
||||||
|
├── groups.ts # Group loading, group-admin access scoping
|
||||||
|
├── i18n.ts # Message language overrides (en/zh)
|
||||||
|
├── send-log.ts # Send logging (logs:send KV keys)
|
||||||
├── token-store.ts # KV-based token CRUD with findUserIdByToken reverse lookup
|
├── token-store.ts # KV-based token CRUD with findUserIdByToken reverse lookup
|
||||||
|
├── home-routes.ts # Landing page routes
|
||||||
|
├── legal-routes.ts # Legal page routes
|
||||||
└── log.ts # JSON console logger (info/warn/error/fatal)
|
└── log.ts # JSON console logger (info/warn/error/fatal)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -37,6 +45,7 @@ src/
|
||||||
| `npm run typecheck` | TypeScript type checking |
|
| `npm run typecheck` | TypeScript type checking |
|
||||||
| `npm run lint` | ESLint (TypeScript) |
|
| `npm run lint` | ESLint (TypeScript) |
|
||||||
| `npm run lint:md` | Markdownlint (Markdown) |
|
| `npm run lint:md` | Markdownlint (Markdown) |
|
||||||
|
| `npm test` | Run unit tests (bun test) |
|
||||||
| `npm run format` | Format all files with Prettier |
|
| `npm run format` | Format all files with Prettier |
|
||||||
| `npm run format:check` | Check Prettier formatting |
|
| `npm run format:check` | Check Prettier formatting |
|
||||||
| `npm run docs:dev` | Start VitePress docs dev server |
|
| `npm run docs:dev` | Start VitePress docs dev server |
|
||||||
|
|
@ -56,10 +65,10 @@ src/
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Functional tests (requires wrangler dev running)
|
# Run the unit test suite (bun test)
|
||||||
bash /tmp/test-webhooker.sh
|
npm test
|
||||||
|
|
||||||
# Or manually
|
# Or manually check the health endpoint
|
||||||
curl http://localhost:8787/health
|
curl http://localhost:8787/health
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,19 @@ WebHooker ships with a built-in config console at `/admin` for managing routes i
|
||||||
|
|
||||||
### Endpoints
|
### Endpoints
|
||||||
|
|
||||||
| Endpoint | Description |
|
| Endpoint | Description |
|
||||||
| ----------------------- | --------------------------- |
|
| ---------------------------------- | --------------------------------------- |
|
||||||
| `GET /admin` | Config console UI |
|
| `GET /admin` | Config console UI |
|
||||||
| `GET /admin/login` | Start GitHub OAuth sign-in |
|
| `GET /admin/login` | Start GitHub OAuth sign-in |
|
||||||
| `GET /admin/logout` | Destroy session |
|
| `GET /admin/logout` | Destroy session |
|
||||||
| `GET /admin/api/routes` | List routes (admin only) |
|
| `GET /admin/api/me` | Current session, scope, and groups |
|
||||||
| `PUT /admin/api/routes` | Replace routes (admin only) |
|
| `GET /admin/api/routes` | List routes (admin only) |
|
||||||
|
| `PUT /admin/api/routes` | Replace routes (admin only) |
|
||||||
|
| `GET /admin/api/groups` | List groups (scoped to access) |
|
||||||
|
| `PUT /admin/api/groups` | Replace groups (super admin only) |
|
||||||
|
| `GET /admin/api/groups/:id/routes` | List a group's routes |
|
||||||
|
| `PUT /admin/api/groups/:id/routes` | Replace a group's routes |
|
||||||
|
| `GET /admin/api/logs` | Send logs (scoped to accessible routes) |
|
||||||
|
|
||||||
The console lets you add, edit, delete, and toggle routes. Saved routes are written to KV `config:routes` immediately and the config cache is invalidated so the webhook pipeline picks them up on the next run.
|
The console lets you add, edit, delete, and toggle routes. Saved routes are written to KV `config:routes` immediately and the config cache is invalidated so the webhook pipeline picks them up on the next run.
|
||||||
|
|
||||||
|
|
@ -58,6 +64,8 @@ There are **no default routes** — each route must define its own target. If no
|
||||||
"id": "unique-route-id",
|
"id": "unique-route-id",
|
||||||
"name": "Human-readable name",
|
"name": "Human-readable name",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"groupId": "my-group",
|
||||||
|
"fallback": false,
|
||||||
"filters": [
|
"filters": [
|
||||||
{ "type": "event", "match": "push" },
|
{ "type": "event", "match": "push" },
|
||||||
{ "type": "repo", "match": "org/repo", "exclude": false }
|
{ "type": "repo", "match": "org/repo", "exclude": false }
|
||||||
|
|
@ -71,6 +79,14 @@ There are **no default routes** — each route must define its own target. If no
|
||||||
|
|
||||||
`target.channelId` is required and used as-is; there is no fallback to a default channel.
|
`target.channelId` is required and used as-is; there is no fallback to a default channel.
|
||||||
|
|
||||||
|
Other route fields:
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
| ---------- | ------- | -------- | ----------------------------------------------------------------------------------------------- |
|
||||||
|
| `groupId` | string | Yes | Id of the [group](#groups) this route belongs to |
|
||||||
|
| `fallback` | boolean | No | When `true`, fires only if no non-fallback route matched the event; its own filters are ignored |
|
||||||
|
| `lang` | string | No | Message language override for this route (e.g. `en`, `zh`); defaults to the global setting |
|
||||||
|
|
||||||
### Custom Route Example
|
### Custom Route Example
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
@ -79,6 +95,7 @@ There are **no default routes** — each route must define its own target. If no
|
||||||
"id": "backend-prs",
|
"id": "backend-prs",
|
||||||
"name": "Backend PRs",
|
"name": "Backend PRs",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"groupId": "backend-team",
|
||||||
"filters": [
|
"filters": [
|
||||||
{ "type": "repo", "match": "myorg/backend" },
|
{ "type": "repo", "match": "myorg/backend" },
|
||||||
{ "type": "event", "match": "pull_request" },
|
{ "type": "event", "match": "pull_request" },
|
||||||
|
|
@ -92,6 +109,35 @@ There are **no default routes** — each route must define its own target. If no
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Groups
|
||||||
|
|
||||||
|
Routes belong to groups. Groups scope admin access and can restrict which events flow into them. They are stored in Cloudflare KV under the key `config:groups` as a JSON array.
|
||||||
|
|
||||||
|
### Group Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "backend-team",
|
||||||
|
"name": "Backend Team",
|
||||||
|
"adminIds": ["rhencloud"],
|
||||||
|
"owners": ["myorg"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
| ---------- | -------- | -------- | ---------------------------------------------------------------------- |
|
||||||
|
| `id` | string | Yes | Lowercase id (`a-z0-9`, `-`); referenced by each route's `groupId` |
|
||||||
|
| `name` | string | Yes | Human-readable group name |
|
||||||
|
| `adminIds` | string[] | Yes | GitHub user IDs or logins who may manage this group's routes |
|
||||||
|
| `owners` | string[] | No | Org/user logins whose events are accepted into this group; empty = all |
|
||||||
|
|
||||||
|
### Access Model
|
||||||
|
|
||||||
|
- **Super admins** (`ADMIN_USER_IDS`) see and edit every group and all routes.
|
||||||
|
- **Group admins** (`adminIds`) only see and edit the groups they manage; submitting a route outside their groups returns `403`.
|
||||||
|
- Group admin endpoints operate on a single group at a time via `/admin/api/groups/:id/routes`; `groupId` is forced from the path parameter.
|
||||||
|
- The `owners` list restricts which event actors (sender logins) the group's routes will dispatch at all.
|
||||||
|
|
||||||
## Filter Types
|
## Filter Types
|
||||||
|
|
||||||
| Type | Matches | Example |
|
| Type | Matches | Example |
|
||||||
|
|
@ -108,7 +154,7 @@ There are **no default routes** — each route must define its own target. If no
|
||||||
- All filters in a route must match for the route to trigger (AND logic)
|
- All filters in a route must match for the route to trigger (AND logic)
|
||||||
- Set `"exclude": true` on any filter to invert it (NOT logic)
|
- Set `"exclude": true` on any filter to invert it (NOT logic)
|
||||||
- `keyword` filter supports regex patterns — falls back to substring match if regex is invalid
|
- `keyword` filter supports regex patterns — falls back to substring match if regex is invalid
|
||||||
- `branch` filter works for push, pull_request, create/delete, workflow_run, and code_scanning_alert events
|
- `branch` filter works for push, pull_request, pull_request_review, pull_request_review_comment, create/delete, workflow_run, and code_scanning_alert events
|
||||||
|
|
||||||
### Match Values
|
### Match Values
|
||||||
|
|
||||||
|
|
@ -121,9 +167,14 @@ Filters accept either a single string or an array of strings:
|
||||||
|
|
||||||
## KV Storage Layout
|
## KV Storage Layout
|
||||||
|
|
||||||
| Key Pattern | Value | TTL |
|
| Key Pattern | Value | TTL |
|
||||||
| ---------------- | --------------------------------- | ------------ |
|
| ------------------------ | --------------------------------------------------- | ------------------ |
|
||||||
| `config:routes` | JSON array of routes | Permanent |
|
| `config:routes` | JSON array of routes | Permanent |
|
||||||
| `session:{id}` | Admin session `{ userId, login }` | 7 days |
|
| `config:groups` | JSON array of groups | Permanent |
|
||||||
| `token:{userId}` | `{ accessToken, expiresAt }` | Until expiry |
|
| `session:{id}` | Admin session `{ userId, login }` | 7 days |
|
||||||
| `state:{hex}` | `{ userId, createdAt }` | 600 seconds |
|
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × token expiry |
|
||||||
|
| `token-reverse:{sha256}` | User id for reverse lookup by token | 0.9 × token expiry |
|
||||||
|
| `discord-link:{userId}` | GitHub user id linked to a Discord user | Permanent |
|
||||||
|
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId? }` | 600 seconds |
|
||||||
|
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
|
||||||
|
| `logs:send:{ts}-{hex}` | Send record | 1 hour |
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ POST /api/comment
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| 字段 | 类型 | 必需 | 说明 |
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------------- | ------ | ---- | ------------------------- |
|
||||||
| `owner` | string | 是 | 仓库所有者 |
|
| `owner` | string | 是 | 仓库所有者 |
|
||||||
| `repo` | string | 是 | 仓库名称 |
|
| `repo` | string | 是 | 仓库名称 |
|
||||||
| `issueNumber` | number | 是 | 议题或 PR 编号 |
|
| `issueNumber` | number | 是 | 议题或 PR 编号 |
|
||||||
| `body` | string | 是 | 评论内容(支持 Markdown) |
|
| `body` | string | 是 | 评论内容(支持 Markdown) |
|
||||||
|
|
||||||
**响应:** `200` 与 GitHub API 响应。
|
**响应:** `200` 与 GitHub API 响应。
|
||||||
|
|
||||||
|
|
@ -57,19 +57,45 @@ POST /api/merge
|
||||||
"owner": "org",
|
"owner": "org",
|
||||||
"repo": "repo",
|
"repo": "repo",
|
||||||
"pullNumber": 42,
|
"pullNumber": 42,
|
||||||
"mergeMethod": "squash"
|
"method": "squash"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| 字段 | 类型 | 必需 | 说明 |
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------------ | ------ | ---- | ----------------------------------------------- |
|
||||||
| `owner` | string | 是 | 仓库所有者 |
|
| `owner` | string | 是 | 仓库所有者 |
|
||||||
| `repo` | string | 是 | 仓库名称 |
|
| `repo` | string | 是 | 仓库名称 |
|
||||||
| `pullNumber` | number | 是 | 拉取请求编号 |
|
| `pullNumber` | number | 是 | 拉取请求编号 |
|
||||||
| `mergeMethod` | string | 否 | `merge`、`squash` 或 `rebase`(默认:`merge`) |
|
| `method` | string | 否 | `merge`、`squash` 或 `rebase`(默认:`squash`) |
|
||||||
|
|
||||||
**响应:** `200` 与 GitHub 合并响应。
|
**响应:** `200` 与 GitHub 合并响应。
|
||||||
|
|
||||||
|
### 关闭拉取请求
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/close
|
||||||
|
```
|
||||||
|
|
||||||
|
不合并、直接关闭拉取请求。
|
||||||
|
|
||||||
|
**请求体:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"owner": "org",
|
||||||
|
"repo": "repo",
|
||||||
|
"pullNumber": 42
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
|
| ------------ | ------ | ---- | ------------ |
|
||||||
|
| `owner` | string | 是 | 仓库所有者 |
|
||||||
|
| `repo` | string | 是 | 仓库名称 |
|
||||||
|
| `pullNumber` | number | 是 | 拉取请求编号 |
|
||||||
|
|
||||||
|
**响应:** `200` 与 GitHub 更新响应。
|
||||||
|
|
||||||
### 添加反应
|
### 添加反应
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -85,16 +111,16 @@ POST /api/react
|
||||||
"owner": "org",
|
"owner": "org",
|
||||||
"repo": "repo",
|
"repo": "repo",
|
||||||
"issueNumber": 42,
|
"issueNumber": 42,
|
||||||
"content": "rocket"
|
"reaction": "rocket"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| 字段 | 类型 | 必需 | 说明 |
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------------- | ------ | ---- | ------------------- |
|
||||||
| `owner` | string | 是 | 仓库所有者 |
|
| `owner` | string | 是 | 仓库所有者 |
|
||||||
| `repo` | string | 是 | 仓库名称 |
|
| `repo` | string | 是 | 仓库名称 |
|
||||||
| `issueNumber` | number | 是 | 议题、PR 或评论编号 |
|
| `issueNumber` | number | 是 | 议题、PR 或评论编号 |
|
||||||
| `content` | string | 是 | 反应类型(见下方) |
|
| `reaction` | string | 是 | 反应类型(见下方) |
|
||||||
|
|
||||||
**反应类型:**
|
**反应类型:**
|
||||||
|
|
||||||
|
|
@ -104,8 +130,8 @@ POST /api/react
|
||||||
|
|
||||||
## 错误响应
|
## 错误响应
|
||||||
|
|
||||||
| 状态码 | 响应体 | 原因 |
|
| 状态码 | 响应体 | 原因 |
|
||||||
| --- | --- | --- |
|
| ------ | --------------------------- | ------------------------- |
|
||||||
| `401` | `{"error": "Unauthorized"}` | 缺少或无效的 Bearer Token |
|
| `401` | `{"error": "Unauthorized"}` | 缺少或无效的 Bearer Token |
|
||||||
| `400` | `{"error": "..."}` | 无效的请求体 |
|
| `400` | `{"error": "..."}` | 无效的请求体 |
|
||||||
| `500` | `{"error": "..."}` | GitHub API 错误 |
|
| `500` | `{"error": "..."}` | GitHub API 错误 |
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ GET /auth/github
|
||||||
|
|
||||||
**查询参数:**
|
**查询参数:**
|
||||||
|
|
||||||
| 参数 | 说明 |
|
| 参数 | 说明 |
|
||||||
| --- | --- |
|
| ---------- | -------------------------------------------------------------------------------------------------------- |
|
||||||
| `userId` | 你的应用用户标识符 |
|
| `redirect` | 可选,登录后返回的相对路径(如 `/admin`)。必须以 `/` 开头但不能以 `//` 开头;任何不安全的值回退为 `/`。 |
|
||||||
|
|
||||||
**响应:** `302` 重定向到 GitHub OAuth 授权 URL。
|
**响应:** `302` 重定向到 GitHub OAuth 授权 URL。
|
||||||
|
|
||||||
|
|
@ -37,12 +37,16 @@ GitHub 授权后重定向到此地址。将 code 交换为访问令牌并存储
|
||||||
|
|
||||||
**查询参数(来自 GitHub):**
|
**查询参数(来自 GitHub):**
|
||||||
|
|
||||||
| 参数 | 说明 |
|
| 参数 | 说明 |
|
||||||
| --- | --- |
|
| ------- | ------------------- |
|
||||||
| `code` | 授权码 |
|
| `code` | 授权码 |
|
||||||
| `state` | CSRF 保护的状态参数 |
|
| `state` | CSRF 保护的状态参数 |
|
||||||
|
|
||||||
**响应:** 重定向到你的 `BASE_URL`,附带成功/失败指示。
|
**响应:**
|
||||||
|
|
||||||
|
- **浏览器流程**(`Accept: text/html`):设置管理员会话 Cookie,然后重定向到 `redirect` 目标;无管理权限的用户被重定向到 `/admin?error=forbidden`。
|
||||||
|
- **JSON 流程**:返回 `{ "userId": "...", "login": "...", "redirectTo": "..." }`。
|
||||||
|
- **Discord 绑定流程**(以未决的 `discordUserId` 启动时):将 Discord 用户绑定到此 GitHub 账号,返回 `{ "ok": true, "discordUserId": "...", "login": "..." }`——浏览器中则显示成功页面。
|
||||||
|
|
||||||
### 撤销 Token
|
### 撤销 Token
|
||||||
|
|
||||||
|
|
@ -66,12 +70,14 @@ Token 以键模式 `token:{userId}` 存储在 KV 中:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"userId": "12345",
|
||||||
"accessToken": "gho_...",
|
"accessToken": "gho_...",
|
||||||
"expiresAt": "2025-01-01T00:00:00.000Z"
|
"expiresAt": 1735689600000,
|
||||||
|
"refreshToken": "..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Token 会根据 `expiresAt` 时间戳自动过期。
|
`expiresAt` 是毫秒级 Unix 时间戳。KV 条目在 Token 有效期的 90% 时过期(至少 60 秒)。反向索引 `token-reverse:{sha256 of token}` 将访问令牌映射回用户 id,使 Bearer 鉴权的端点能解析调用者。与 GitHub 账号绑定的 Discord 用户存储在 `discord-link:{discordUserId}` 下。
|
||||||
|
|
||||||
## 使用 Token
|
## 使用 Token
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,34 @@ https://your-worker.workers.dev
|
||||||
|
|
||||||
## 端点
|
## 端点
|
||||||
|
|
||||||
| 方法 | 路径 | 鉴权 | 说明 |
|
| 方法 | 路径 | 鉴权 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| -------- | ------------------------------ | ------------ | ------------------------ |
|
||||||
| `GET` | `/health` | 无 | 健康检查 |
|
| `GET` | `/health` | 无 | 健康检查 |
|
||||||
| `POST` | `/webhook` | HMAC 签名 | GitHub webhook 接入 |
|
| `POST` | `/webhook` | HMAC 签名 | GitHub webhook 接入 |
|
||||||
| `GET` | `/auth/github` | 无 | 启动 GitHub OAuth 流程 |
|
| `GET` | `/auth/github` | 无 | 启动 GitHub OAuth 流程 |
|
||||||
| `GET` | `/auth/github/callback` | 无 | OAuth 回调 |
|
| `GET` | `/auth/github/callback` | 无 | OAuth 回调 |
|
||||||
| `DELETE` | `/auth/token/:userId` | 无 | 撤销用户 Token |
|
| `DELETE` | `/auth/token/:userId` | 无 | 撤销用户 Token |
|
||||||
| `POST` | `/api/comment` | Bearer Token | 创建议题评论 |
|
| `POST` | `/api/comment` | Bearer Token | 创建议题评论 |
|
||||||
| `POST` | `/api/merge` | Bearer Token | 合并拉取请求 |
|
| `POST` | `/api/merge` | Bearer Token | 合并拉取请求 |
|
||||||
| `POST` | `/api/react` | Bearer Token | 添加议题反应 |
|
| `POST` | `/api/close` | Bearer Token | 关闭拉取请求 |
|
||||||
|
| `POST` | `/api/react` | Bearer Token | 添加议题反应 |
|
||||||
|
| `GET` | `/admin` | 管理员会话 | 配置控制台页面 |
|
||||||
|
| `GET` | `/admin/api/routes` | 管理员会话 | 列出路由 |
|
||||||
|
| `PUT` | `/admin/api/routes` | 管理员会话 | 替换路由 |
|
||||||
|
| `GET` | `/admin/api/groups` | 管理员会话 | 列出分组(按权限过滤) |
|
||||||
|
| `PUT` | `/admin/api/groups` | 管理员会话 | 替换分组(仅超级管理员) |
|
||||||
|
| `GET` | `/admin/api/groups/:id/routes` | 管理员会话 | 列出某分组的路由 |
|
||||||
|
| `PUT` | `/admin/api/groups/:id/routes` | 管理员会话 | 替换某分组的路由 |
|
||||||
|
| `GET` | `/admin/api/me` | 管理员会话 | 当前会话信息 |
|
||||||
|
| `GET` | `/admin/api/logs` | 管理员会话 | 发送日志(按权限过滤) |
|
||||||
|
|
||||||
|
## 管理控制台
|
||||||
|
|
||||||
|
参见[配置 → Web 控制台](../guide/configuration.md#web-ui)了解设置方法。管理端点需要会话 Cookie,可通过 `GET /admin/login`(GitHub OAuth)获取;登录用户必须列在 `ADMIN_USER_IDS` 中。
|
||||||
|
|
||||||
|
- `GET /admin` — 提供配置控制台 HTML
|
||||||
|
- `GET /admin/api/routes` — 返回 `{ "routes": Route[] }`
|
||||||
|
- `PUT /admin/api/routes` — 请求体为 `{ "routes": Route[] }`;校验每条路由(id 格式、唯一 id、name、enabled、groupId、过滤器、字符串 `target.channelId`)并持久化到 KV `config:routes`。返回 `200 { ok, count }` 或 `400 { error }` / `401 { error }`。
|
||||||
|
|
||||||
## 健康检查
|
## 健康检查
|
||||||
|
|
||||||
|
|
@ -45,11 +63,11 @@ POST /webhook
|
||||||
|
|
||||||
**请求头:**
|
**请求头:**
|
||||||
|
|
||||||
| 头部 | 必需 | 说明 |
|
| 头部 | 必需 | 说明 |
|
||||||
| --- | --- | --- |
|
| --------------------- | ---- | ---------------- |
|
||||||
| `X-Hub-Signature-256` | 是 | HMAC-SHA256 签名 |
|
| `X-Hub-Signature-256` | 是 | HMAC-SHA256 签名 |
|
||||||
| `X-GitHub-Event` | 是 | 事件类型名称 |
|
| `X-GitHub-Event` | 是 | 事件类型名称 |
|
||||||
| `X-GitHub-Delivery` | 是 | 唯一投递 ID |
|
| `X-GitHub-Delivery` | 是 | 唯一投递 ID |
|
||||||
|
|
||||||
**请求体:** GitHub webhook JSON 载荷(最大 1MB)。
|
**请求体:** GitHub webhook JSON 载荷(最大 1MB)。
|
||||||
|
|
||||||
|
|
@ -63,11 +81,11 @@ POST /webhook
|
||||||
|
|
||||||
**错误响应:**
|
**错误响应:**
|
||||||
|
|
||||||
| 状态码 | 响应体 | 原因 |
|
| 状态码 | 响应体 | 原因 |
|
||||||
| --- | --- | --- |
|
| ------ | -------------------------------- | ---------------------------- |
|
||||||
| `401` | `{"error": "Invalid signature"}` | 签名验证失败 |
|
| `401` | `{"error": "Invalid signature"}` | 签名验证失败 |
|
||||||
| `400` | `{"error": "Invalid event"}` | 缺少事件头或格式错误的请求体 |
|
| `400` | `{"error": "Invalid event"}` | 缺少事件头或格式错误的请求体 |
|
||||||
| `413` | `{"error": "Request too large"}` | 请求体超过 1MB 限制 |
|
| `413` | `{"error": "Request too large"}` | 请求体超过 1MB 限制 |
|
||||||
|
|
||||||
## 错误格式
|
## 错误格式
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,31 +16,40 @@ npm run dev # 启动本地开发服务器
|
||||||
src/
|
src/
|
||||||
├── index.ts # CF Workers 入口 (fetch + scheduled),导出 DiscordGateway DO
|
├── index.ts # CF Workers 入口 (fetch + scheduled),导出 DiscordGateway DO
|
||||||
├── types.ts # Env、Config、Route、Filter、WebhookEvent、FormattedMessage
|
├── types.ts # Env、Config、Route、Filter、WebhookEvent、FormattedMessage
|
||||||
├── config.ts # 从 KV 加载路由(回退到 7 条默认),从 env 构建 Config
|
├── config.ts # 从 KV 加载路由(未设置时返回 []),从 env 构建 Config
|
||||||
├── server.ts # Hono 应用: /health、/webhook,挂载 /auth + /
|
├── server.ts # Hono 应用: /health、/webhook,挂载 /auth、/admin + /
|
||||||
├── webhook.ts # HMAC 验证 (Web Crypto)、parseEvent、extractBranch、matchRoute
|
├── webhook.ts # HMAC 验证 (Web Crypto)、parseEvent、extractBranch、matchRoute
|
||||||
├── discord.ts # 通过 DO RPC 分发、initGateway (scheduled)
|
├── discord.ts # 通过 Discord REST 分发(启用 Gateway 时走 DO RPC)、initGateway (scheduled)
|
||||||
|
├── discord-rest.ts # Discord REST sendMessage,带重试和限流处理
|
||||||
├── discord-gateway.ts # Durable Object: Discord Gateway WS、心跳、频道缓存、发送
|
├── discord-gateway.ts # Durable Object: Discord Gateway WS、心跳、频道缓存、发送
|
||||||
├── formatter.ts # 23 种事件格式化器 + 通用回退
|
├── formatter.ts # 23 种事件格式化器 + 通用回退
|
||||||
├── github-oauth.ts # OAuth URL、回调 Token 交换、getUserOctokit
|
├── github-oauth.ts # OAuth URL、回调 Token 交换、getUserOctokit
|
||||||
├── oauth-routes.ts # GET /auth/github、回调、DELETE /token/:userId (KV 状态)
|
├── oauth-routes.ts # GET /auth/github、回调、DELETE /token/:userId (KV 状态)
|
||||||
├── action-routes.ts # POST /api/comment|merge|react (通过 KV 查找进行 Bearer Token 鉴权)
|
├── action-routes.ts # POST /api/comment|merge|close|react (通过 KV 查找进行 Bearer Token 鉴权)
|
||||||
|
├── admin-routes.ts # /admin API:路由、分组、me、日志(会话 + 权限范围鉴权)
|
||||||
|
├── admin-session.ts # 管理员会话 CRUD (KV session:{id})、Cookie 辅助函数
|
||||||
|
├── groups.ts # 分组加载、分组管理员权限范围
|
||||||
|
├── i18n.ts # 消息语言覆盖 (en/zh)
|
||||||
|
├── send-log.ts # 发送日志 (logs:send KV 键)
|
||||||
├── token-store.ts # 基于 KV 的 Token CRUD,带 findUserIdByToken 反向查找
|
├── token-store.ts # 基于 KV 的 Token CRUD,带 findUserIdByToken 反向查找
|
||||||
|
├── home-routes.ts # 落地页路由
|
||||||
|
├── legal-routes.ts # 法律页面路由
|
||||||
└── log.ts # JSON 控制台日志 (info/warn/error/fatal)
|
└── log.ts # JSON 控制台日志 (info/warn/error/fatal)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 脚本
|
## 脚本
|
||||||
|
|
||||||
| 命令 | 说明 |
|
| 命令 | 说明 |
|
||||||
| --- | --- |
|
| ---------------------- | ----------------------------- |
|
||||||
| `npm run dev` | 启动 wrangler dev 服务器 |
|
| `npm run dev` | 启动 wrangler dev 服务器 |
|
||||||
| `npm run typecheck` | TypeScript 类型检查 |
|
| `npm run typecheck` | TypeScript 类型检查 |
|
||||||
| `npm run lint` | ESLint (TypeScript) |
|
| `npm run lint` | ESLint (TypeScript) |
|
||||||
| `npm run lint:md` | Markdownlint (Markdown) |
|
| `npm run lint:md` | Markdownlint (Markdown) |
|
||||||
| `npm run format` | 使用 Prettier 格式化所有文件 |
|
| `npm test` | 运行单元测试 (bun test) |
|
||||||
| `npm run format:check` | 检查 Prettier 格式 |
|
| `npm run format` | 使用 Prettier 格式化所有文件 |
|
||||||
| `npm run docs:dev` | 启动 VitePress 文档开发服务器 |
|
| `npm run format:check` | 检查 Prettier 格式 |
|
||||||
| `npm run docs:build` | 构建文档站点 |
|
| `npm run docs:dev` | 启动 VitePress 文档开发服务器 |
|
||||||
|
| `npm run docs:build` | 构建文档站点 |
|
||||||
|
|
||||||
## 代码风格
|
## 代码风格
|
||||||
|
|
||||||
|
|
@ -56,10 +65,10 @@ src/
|
||||||
## 测试
|
## 测试
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 功能测试(需要正在运行的 wrangler dev)
|
# 运行单元测试套件 (bun test)
|
||||||
bash /tmp/test-webhooker.sh
|
npm test
|
||||||
|
|
||||||
# 或手动
|
# 或手动检查健康端点
|
||||||
curl http://localhost:8787/health
|
curl http://localhost:8787/health
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,19 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
|
|
||||||
### 端点
|
### 端点
|
||||||
|
|
||||||
| 端点 | 说明 |
|
| 端点 | 说明 |
|
||||||
| ----------------------- | ---------------------- |
|
| ---------------------------------- | ---------------------------- |
|
||||||
| `GET /admin` | 配置控制台页面 |
|
| `GET /admin` | 配置控制台页面 |
|
||||||
| `GET /admin/login` | 开始 GitHub OAuth 登录 |
|
| `GET /admin/login` | 开始 GitHub OAuth 登录 |
|
||||||
| `GET /admin/logout` | 销毁会话 |
|
| `GET /admin/logout` | 销毁会话 |
|
||||||
| `GET /admin/api/routes` | 列出路由(仅管理员) |
|
| `GET /admin/api/me` | 当前会话、权限范围和分组 |
|
||||||
| `PUT /admin/api/routes` | 替换路由(仅管理员) |
|
| `GET /admin/api/routes` | 列出路由(仅管理员) |
|
||||||
|
| `PUT /admin/api/routes` | 替换路由(仅管理员) |
|
||||||
|
| `GET /admin/api/groups` | 列出分组(按权限过滤) |
|
||||||
|
| `PUT /admin/api/groups` | 替换分组(仅超级管理员) |
|
||||||
|
| `GET /admin/api/groups/:id/routes` | 列出某分组的路由 |
|
||||||
|
| `PUT /admin/api/groups/:id/routes` | 替换某分组的路由 |
|
||||||
|
| `GET /admin/api/logs` | 发送日志(按可访问路由过滤) |
|
||||||
|
|
||||||
控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。
|
控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。
|
||||||
|
|
||||||
|
|
@ -58,6 +64,8 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
"id": "unique-route-id",
|
"id": "unique-route-id",
|
||||||
"name": "可读名称",
|
"name": "可读名称",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"groupId": "my-group",
|
||||||
|
"fallback": false,
|
||||||
"filters": [
|
"filters": [
|
||||||
{ "type": "event", "match": "push" },
|
{ "type": "event", "match": "push" },
|
||||||
{ "type": "repo", "match": "org/repo", "exclude": false }
|
{ "type": "repo", "match": "org/repo", "exclude": false }
|
||||||
|
|
@ -71,6 +79,14 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
|
|
||||||
`target.channelId` 必填且按原样使用,不存在默认频道回退。
|
`target.channelId` 必填且按原样使用,不存在默认频道回退。
|
||||||
|
|
||||||
|
其他路由字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
|
| ---------- | ------- | ---- | ---------------------------------------------------------------------- |
|
||||||
|
| `groupId` | string | 是 | 该路由所属[分组](#分组)的 id |
|
||||||
|
| `fallback` | boolean | 否 | 为 `true` 时,仅当没有其它路由匹配该事件时才发送,其自身过滤器会被忽略 |
|
||||||
|
| `lang` | string | 否 | 该路由的消息语言覆盖(如 `en`、`zh`),默认跟随全局设置 |
|
||||||
|
|
||||||
### 自定义路由示例
|
### 自定义路由示例
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
@ -79,6 +95,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
"id": "backend-prs",
|
"id": "backend-prs",
|
||||||
"name": "后端 PR",
|
"name": "后端 PR",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"groupId": "backend-team",
|
||||||
"filters": [
|
"filters": [
|
||||||
{ "type": "repo", "match": "myorg/backend" },
|
{ "type": "repo", "match": "myorg/backend" },
|
||||||
{ "type": "event", "match": "pull_request" },
|
{ "type": "event", "match": "pull_request" },
|
||||||
|
|
@ -92,6 +109,35 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 分组
|
||||||
|
|
||||||
|
路由隶属于分组。分组用于限定管理权限,并可限制哪些事件允许流入。它们以 JSON 数组形式存储在 Cloudflare KV 中,键为 `config:groups`。
|
||||||
|
|
||||||
|
### 分组模式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "backend-team",
|
||||||
|
"name": "后端团队",
|
||||||
|
"adminIds": ["rhencloud"],
|
||||||
|
"owners": ["myorg"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
|
| ---------- | -------- | ---- | ----------------------------------------------------- |
|
||||||
|
| `id` | string | 是 | 小写 id(`a-z0-9`、`-`),由每条路由的 `groupId` 引用 |
|
||||||
|
| `name` | string | 是 | 可读的分组名称 |
|
||||||
|
| `adminIds` | string[] | 是 | 可管理该分组路由的 GitHub 用户 ID 或登录名 |
|
||||||
|
| `owners` | string[] | 否 | 允许事件进入该分组的组织/用户登录名;为空表示不限制 |
|
||||||
|
|
||||||
|
### 权限模型
|
||||||
|
|
||||||
|
- **超级管理员**(`ADMIN_USER_IDS`)可查看和编辑所有分组及全部路由。
|
||||||
|
- **分组管理员**(`adminIds`)只能查看和编辑其管理的分组;提交其分组之外的路由返回 `403`。
|
||||||
|
- 分组管理端点通过 `/admin/api/groups/:id/routes` 一次只操作一个分组;`groupId` 由路径参数强制指定。
|
||||||
|
- `owners` 列表限定哪些事件参与者(发送者登录名)的事件会被该分组的路由投递。
|
||||||
|
|
||||||
## 过滤器类型
|
## 过滤器类型
|
||||||
|
|
||||||
| 类型 | 匹配对象 | 示例 |
|
| 类型 | 匹配对象 | 示例 |
|
||||||
|
|
@ -108,7 +154,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
- 路由中的所有过滤器必须都匹配才触发路由(AND 逻辑)
|
- 路由中的所有过滤器必须都匹配才触发路由(AND 逻辑)
|
||||||
- 在任何过滤器上设置 `"exclude": true` 可反转匹配逻辑(NOT 逻辑)
|
- 在任何过滤器上设置 `"exclude": true` 可反转匹配逻辑(NOT 逻辑)
|
||||||
- `keyword` 过滤器支持正则表达式——如果正则有误,回退到子串匹配
|
- `keyword` 过滤器支持正则表达式——如果正则有误,回退到子串匹配
|
||||||
- `branch` 过滤器适用于 push、pull_request、create/delete、workflow_run 和 code_scanning_alert 事件
|
- `branch` 过滤器适用于 push、pull_request、pull_request_review、pull_request_review_comment、create/delete、workflow_run 和 code_scanning_alert 事件
|
||||||
|
|
||||||
### 匹配值
|
### 匹配值
|
||||||
|
|
||||||
|
|
@ -121,9 +167,14 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
|
|
||||||
## KV 存储布局
|
## KV 存储布局
|
||||||
|
|
||||||
| 键模式 | 值 | TTL |
|
| 键模式 | 值 | TTL |
|
||||||
| ---------------- | ------------------------------ | ------ |
|
| ------------------------ | --------------------------------------------------- | ------------------ |
|
||||||
| `config:routes` | JSON 路由数组 | 永久 |
|
| `config:routes` | JSON 路由数组 | 永久 |
|
||||||
| `session:{id}` | 管理员会话 `{ userId, login }` | 7 天 |
|
| `config:groups` | JSON 分组数组 | 永久 |
|
||||||
| `token:{userId}` | `{ accessToken, expiresAt }` | 至过期 |
|
| `session:{id}` | 管理员会话 `{ userId, login }` | 7 天 |
|
||||||
| `state:{hex}` | `{ userId, createdAt }` | 600 秒 |
|
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × Token 有效期 |
|
||||||
|
| `token-reverse:{sha256}` | 用于按 Token 反查的用户 id | 0.9 × Token 有效期 |
|
||||||
|
| `discord-link:{userId}` | 与 Discord 用户绑定的 GitHub 用户 id | 永久 |
|
||||||
|
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId? }` | 600 秒 |
|
||||||
|
| `delivery:{id}` | Webhook 投递 id(去重标记) | 300 秒 |
|
||||||
|
| `logs:send:{ts}-{hex}` | 发送记录 | 1 小时 |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue