WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto

Add Bruno API collections for testing and documentation (#16)

* tooling: add Bruno API collections for testing and documentation

Adds Bruno (git-friendly API client) collections for all AppView endpoints:
- Health check, auth (OAuth flow), forum, categories, topics, posts
- Pre-configured environments for local and dev
- Inline documentation and response assertions
- README with setup instructions and usage guide

Bruno's plain-text .bru format provides version-controlled API documentation
that stays in sync with code changes.

* docs: add Bruno collection maintenance guidelines to CLAUDE.md

Ensures Bruno collections stay synchronized with API changes by:
- Requiring Bruno updates in the same commit as route changes
- Providing template and best practices for .bru files
- Documenting when/how to update collections
- Emphasizing Bruno files as living API documentation

authored by

Malpercio and committed by
GitHub
97864513 9a93b3e9

+632
+137
CLAUDE.md
··· 488 488 489 489 **Why this matters:** The plan document and Linear can drift from reality as code evolves. Regular synchronization prevents rediscovering completed work and ensures accurate project status. 490 490 491 + ## Bruno API Collections 492 + 493 + **CRITICAL: Keep Bruno collections synchronized with API changes.** 494 + 495 + The `bruno/` directory contains [Bruno](https://www.usebruno.com/) collections that serve dual purpose: 496 + 1. **Interactive API testing** during development 497 + 2. **Version-controlled API documentation** that stays in sync with code 498 + 499 + ### When to Update Bruno Collections 500 + 501 + **When adding a new API endpoint:** 502 + 1. Create a new `.bru` file in the appropriate `bruno/AppView API/` subdirectory 503 + 2. Follow the naming pattern: use descriptive names like `Create Topic.bru`, `Get Forum Metadata.bru` 504 + 3. Include all request details: method, URL with variables, headers, body (if POST/PUT) 505 + 4. Add comprehensive documentation in the `docs` block explaining: 506 + - Required/optional parameters 507 + - Expected response format with example 508 + - All possible error codes (400, 401, 404, 500, 503) 509 + - Authentication requirements 510 + - Validation rules 511 + 5. Add assertions to validate responses automatically 512 + 513 + **When modifying an existing endpoint:** 514 + 1. Update the corresponding `.bru` file in `bruno/AppView API/` 515 + 2. Update parameter descriptions if inputs changed 516 + 3. Update response documentation if output format changed 517 + 4. Update error documentation if new error cases added 518 + 5. Update assertions if validation logic changed 519 + 520 + **When removing an endpoint:** 521 + 1. Delete the corresponding `.bru` file 522 + 2. Update `bruno/README.md` if it referenced the removed endpoint 523 + 524 + **When adding new environment variables:** 525 + 1. Update `bruno/environments/local.bru` with local development values 526 + 2. Update `bruno/environments/dev.bru` with deployment values 527 + 3. Document the variable in `bruno/README.md` under "Environment Variables Reference" 528 + 529 + ### Bruno File Template 530 + 531 + When creating new `.bru` files, follow this template: 532 + 533 + ```bru 534 + meta { 535 + name: Endpoint Name 536 + type: http 537 + seq: 1 538 + } 539 + 540 + get { 541 + url: {{appview_url}}/api/path 542 + } 543 + 544 + params:query { 545 + param1: {{variable}} 546 + } 547 + 548 + headers { 549 + Content-Type: application/json 550 + } 551 + 552 + body:json { 553 + { 554 + "field": "value" 555 + } 556 + } 557 + 558 + assert { 559 + res.status: eq 200 560 + res.body.field: isDefined 561 + } 562 + 563 + docs { 564 + Brief description of what this endpoint does. 565 + 566 + Path/query/body params: 567 + - param1: Description (type, required/optional) 568 + 569 + Returns: 570 + { 571 + "field": "value" 572 + } 573 + 574 + Error codes: 575 + - 400: Bad request (invalid input) 576 + - 401: Unauthorized (requires auth) 577 + - 404: Not found 578 + - 500: Server error 579 + 580 + Notes: 581 + - Any special considerations or validation rules 582 + } 583 + ``` 584 + 585 + ### Workflow Integration 586 + 587 + **When committing API changes, update Bruno collections in the SAME commit:** 588 + 589 + ```sh 590 + # Example: Adding a new endpoint 591 + git add apps/appview/src/routes/my-route.ts 592 + git add apps/appview/src/routes/__tests__/my-route.test.ts 593 + git add bruno/AppView\ API/MyRoute/New\ Endpoint.bru 594 + git commit -m "feat: add new endpoint for X 595 + 596 + - Implements POST /api/my-endpoint 597 + - Adds validation for Y 598 + - Updates Bruno collection with request documentation" 599 + ``` 600 + 601 + **Why commit together:** Bruno collections are API documentation. Keeping them in the same commit ensures the documentation is never out of sync with the implementation. 602 + 603 + ### Testing Bruno Collections 604 + 605 + Before committing: 606 + 1. Open the collection in Bruno 607 + 2. Test each modified request against your local dev server (`pnpm dev`) 608 + 3. Verify assertions pass (green checkmarks) 609 + 4. Verify documentation is accurate and complete 610 + 5. Check that error scenarios are documented (not just happy path) 611 + 612 + ### Common Mistakes 613 + 614 + **DON'T:** 615 + - Commit API changes without updating Bruno collections 616 + - Use hardcoded URLs instead of environment variables (`{{appview_url}}`) 617 + - Skip documenting error cases (only document 200 responses) 618 + - Leave placeholder/example data that doesn't match actual API behavior 619 + - Forget to update assertions when response format changes 620 + 621 + **DO:** 622 + - Update Bruno files in the same commit as route implementation 623 + - Use environment variables for all URLs and test data 624 + - Document all HTTP status codes the endpoint can return 625 + - Include example request/response bodies that match actual behavior 626 + - Test requests locally before committing 627 + 491 628 ## Git Conventions 492 629 493 630 - Do not include `Co-Authored-By` lines in commit messages.
+5
bruno/.gitignore
··· 1 + # Bruno local state 2 + .bruno/ 3 + 4 + # Environment files with secrets (if needed) 5 + # environments/local.bru
+23
bruno/AppView API/Auth/Check Session.bru
··· 1 + meta { 2 + name: Check Session 3 + type: http 4 + seq: 2 5 + } 6 + 7 + get { 8 + url: {{appview_url}}/api/auth/session 9 + } 10 + 11 + docs { 12 + Check current authentication status. 13 + 14 + Returns 401 if not authenticated. 15 + Returns 200 with session info if authenticated: 16 + { 17 + "authenticated": true, 18 + "did": "did:plc:...", 19 + "sub": "..." 20 + } 21 + 22 + Requires valid session cookie (atbb_session). 23 + }
+28
bruno/AppView API/Auth/Login.bru
··· 1 + meta { 2 + name: Login 3 + type: http 4 + seq: 1 5 + } 6 + 7 + get { 8 + url: {{appview_url}}/api/auth/login?handle={{user_handle}} 9 + } 10 + 11 + params:query { 12 + handle: {{user_handle}} 13 + } 14 + 15 + docs { 16 + Initiate OAuth login flow. 17 + 18 + The server will: 19 + 1. Resolve the handle to a DID and PDS 20 + 2. Generate PKCE challenge 21 + 3. Redirect to the user's PDS for authorization 22 + 23 + Required query param: 24 + - handle: User's AT Protocol handle (e.g., user.bsky.social) 25 + 26 + Note: This will redirect to the PDS, so you'll need to follow the OAuth flow 27 + in a browser to complete authentication. 28 + }
+21
bruno/AppView API/Auth/Logout.bru
··· 1 + meta { 2 + name: Logout 3 + type: http 4 + seq: 3 5 + } 6 + 7 + get { 8 + url: {{appview_url}}/api/auth/logout 9 + } 10 + 11 + docs { 12 + Clear session and revoke OAuth tokens. 13 + 14 + This will: 15 + 1. Revoke tokens at the user's PDS 16 + 2. Delete the server-side session 17 + 3. Clear the session cookie 18 + 19 + Optional query param: 20 + - redirect: Relative path to redirect to after logout (e.g., /) 21 + }
+37
bruno/AppView API/Categories/List Categories.bru
··· 1 + meta { 2 + name: List Categories 3 + type: http 4 + seq: 1 5 + } 6 + 7 + get { 8 + url: {{appview_url}}/api/categories 9 + } 10 + 11 + assert { 12 + res.status: eq 200 13 + res.body.categories: isArray 14 + } 15 + 16 + docs { 17 + List all forum categories, ordered by sortOrder. 18 + 19 + Returns: 20 + { 21 + "categories": [ 22 + { 23 + "id": "1", 24 + "did": "did:plc:...", 25 + "name": "General", 26 + "description": "General discussion", 27 + "slug": "general", 28 + "sortOrder": 0, 29 + "forumId": "1", 30 + "createdAt": "2024-01-01T00:00:00.000Z", 31 + "indexedAt": "2024-01-01T00:00:00.000Z" 32 + } 33 + ] 34 + } 35 + 36 + Maximum 1000 categories returned (defensive limit). 37 + }
+30
bruno/AppView API/Forum/Get Forum Metadata.bru
··· 1 + meta { 2 + name: Get Forum Metadata 3 + type: http 4 + seq: 1 5 + } 6 + 7 + get { 8 + url: {{appview_url}}/api/forum 9 + } 10 + 11 + assert { 12 + res.status: eq 200 13 + res.body.did: isDefined 14 + res.body.name: isDefined 15 + } 16 + 17 + docs { 18 + Retrieve forum metadata (singleton record). 19 + 20 + Returns: 21 + { 22 + "id": "1", 23 + "did": "did:plc:...", 24 + "name": "Forum Name", 25 + "description": "Forum description", 26 + "indexedAt": "2024-01-01T00:00:00.000Z" 27 + } 28 + 29 + Returns 404 if forum has not been initialized. 30 + }
+14
bruno/AppView API/Health/Check Health.bru
··· 1 + meta { 2 + name: Check Health 3 + type: http 4 + seq: 1 5 + } 6 + 7 + get { 8 + url: {{appview_url}}/api/healthz 9 + } 10 + 11 + docs { 12 + Simple health check endpoint for monitoring. 13 + Returns 200 OK if the service is running. 14 + }
+59
bruno/AppView API/Posts/Create Reply.bru
··· 1 + meta { 2 + name: Create Reply 3 + type: http 4 + seq: 1 5 + } 6 + 7 + post { 8 + url: {{appview_url}}/api/posts 9 + } 10 + 11 + headers { 12 + Content-Type: application/json 13 + } 14 + 15 + body:json { 16 + { 17 + "text": "This is a reply", 18 + "rootPostId": "1", 19 + "parentPostId": "1" 20 + } 21 + } 22 + 23 + assert { 24 + res.status: eq 201 25 + res.body.uri: isDefined 26 + res.body.cid: isDefined 27 + res.body.rkey: isDefined 28 + } 29 + 30 + docs { 31 + Create a reply to an existing post. 32 + 33 + Required body: 34 + { 35 + "text": "Reply text (1-10000 chars, trimmed)", 36 + "rootPostId": "1", // Topic (thread starter) ID 37 + "parentPostId": "1" // Direct parent post ID (can be same as rootPostId) 38 + } 39 + 40 + Returns: 41 + { 42 + "uri": "at://did:plc:.../space.atbb.post/...", 43 + "cid": "...", 44 + "rkey": "..." 45 + } 46 + 47 + Requires authentication (valid session cookie). 48 + 49 + Validation: 50 + - Both rootPostId and parentPostId must exist 51 + - Parent must belong to the same thread (parent.rootPostId == rootPostId OR parent is root) 52 + - Root post must have a forum reference 53 + 54 + Returns 400 for invalid input or validation failures. 55 + Returns 401 if not authenticated. 56 + Returns 404 if root or parent post not found. 57 + Returns 503 if PDS unreachable (network error). 58 + Returns 500 for server errors. 59 + }
+52
bruno/AppView API/Topics/Create Topic.bru
··· 1 + meta { 2 + name: Create Topic 3 + type: http 4 + seq: 2 5 + } 6 + 7 + post { 8 + url: {{appview_url}}/api/topics 9 + } 10 + 11 + headers { 12 + Content-Type: application/json 13 + } 14 + 15 + body:json { 16 + { 17 + "text": "This is a new topic", 18 + "forumUri": "at://{{forum_did}}/space.atbb.forum.forum/self" 19 + } 20 + } 21 + 22 + assert { 23 + res.status: eq 201 24 + res.body.uri: isDefined 25 + res.body.cid: isDefined 26 + res.body.rkey: isDefined 27 + } 28 + 29 + docs { 30 + Create a new topic (thread starter post). 31 + 32 + Required body: 33 + { 34 + "text": "Post text (1-10000 chars, trimmed)", 35 + "forumUri": "at://did:plc:.../space.atbb.forum.forum/self" (optional, defaults to singleton forum) 36 + } 37 + 38 + Returns: 39 + { 40 + "uri": "at://did:plc:.../space.atbb.post/...", 41 + "cid": "...", 42 + "rkey": "..." 43 + } 44 + 45 + Requires authentication (valid session cookie). 46 + 47 + Returns 400 for invalid text. 48 + Returns 401 if not authenticated. 49 + Returns 404 if forum not found. 50 + Returns 503 if PDS unreachable (network error). 51 + Returns 500 for server errors. 52 + }
+62
bruno/AppView API/Topics/Get Topic.bru
··· 1 + meta { 2 + name: Get Topic 3 + type: http 4 + seq: 1 5 + } 6 + 7 + get { 8 + url: {{appview_url}}/api/topics/{{topic_id}} 9 + } 10 + 11 + params:path { 12 + topic_id: 1 13 + } 14 + 15 + assert { 16 + res.status: eq 200 17 + res.body.topicId: isDefined 18 + res.body.post: isDefined 19 + res.body.replies: isArray 20 + } 21 + 22 + docs { 23 + Get a topic (thread starter post) with all replies. 24 + 25 + Path params: 26 + - id: Topic post ID (bigint as string) 27 + 28 + Returns: 29 + { 30 + "topicId": "1", 31 + "post": { 32 + "id": "1", 33 + "did": "did:plc:...", 34 + "rkey": "...", 35 + "text": "Topic text", 36 + "forumUri": "at://did:plc:.../space.atbb.forum.forum/self", 37 + "createdAt": "2024-01-01T00:00:00.000Z", 38 + "author": { 39 + "did": "did:plc:...", 40 + "handle": "user.bsky.social" 41 + } 42 + }, 43 + "replies": [ 44 + { 45 + "id": "2", 46 + "did": "did:plc:...", 47 + "rkey": "...", 48 + "text": "Reply text", 49 + "parentPostId": "1", 50 + "createdAt": "2024-01-01T00:00:00.000Z", 51 + "author": { 52 + "did": "did:plc:...", 53 + "handle": "user.bsky.social" 54 + } 55 + } 56 + ] 57 + } 58 + 59 + Returns 400 if ID is invalid. 60 + Returns 404 if topic not found. 61 + Maximum 1000 replies returned (defensive limit). 62 + }
+145
bruno/README.md
··· 1 + # atBB Bruno API Collections 2 + 3 + This directory contains [Bruno](https://www.usebruno.com/) API collections for testing and documenting the atBB forum API. 4 + 5 + ## Structure 6 + 7 + ``` 8 + bruno/ 9 + ├── environments/ # Environment configurations 10 + │ ├── local.bru # Local development (default) 11 + │ └── dev.bru # Development server 12 + └── AppView API/ # AppView API endpoints 13 + ├── Health/ # Health check 14 + ├── Auth/ # OAuth authentication flow 15 + ├── Forum/ # Forum metadata 16 + ├── Categories/ # Category management 17 + ├── Topics/ # Topic (thread) operations 18 + └── Posts/ # Post (reply) operations 19 + ``` 20 + 21 + ## Getting Started 22 + 23 + ### 1. Install Bruno 24 + 25 + Download and install Bruno from [usebruno.com](https://www.usebruno.com/) or via package manager: 26 + 27 + ```sh 28 + # macOS (Homebrew) 29 + brew install bruno 30 + 31 + # Arch Linux 32 + yay -S bruno-bin 33 + 34 + # Or download from GitHub releases 35 + ``` 36 + 37 + ### 2. Open the Collection 38 + 39 + 1. Launch Bruno 40 + 2. Click "Open Collection" 41 + 3. Navigate to `/path/to/atbb-monorepo/bruno` 42 + 4. Select the `bruno` folder 43 + 44 + ### 3. Start Your Local Servers 45 + 46 + ```sh 47 + # In the monorepo root 48 + pnpm dev 49 + ``` 50 + 51 + This will start: 52 + - AppView API at `http://localhost:3000` 53 + - Web UI at `http://localhost:3001` 54 + 55 + ### 4. Configure Environment Variables 56 + 57 + The `local` environment is pre-configured for local development. Update these variables in `environments/local.bru` as needed: 58 + 59 + - `appview_url` - AppView API base URL (default: `http://localhost:3000`) 60 + - `web_url` - Web UI base URL (default: `http://localhost:3001`) 61 + - `forum_did` - Forum DID from your `.env` file 62 + - `user_handle` - Your AT Protocol handle for testing auth (e.g., `user.bsky.social`) 63 + - `topic_id` - A valid topic ID for testing topic retrieval 64 + 65 + ## API Overview 66 + 67 + ### Health Check 68 + 69 + - **GET /api/healthz** - Service health status 70 + 71 + ### Authentication 72 + 73 + OAuth flow using `@atproto/oauth-client-node`: 74 + 75 + 1. **GET /api/auth/login?handle=user.bsky.social** - Initiate login 76 + 2. **GET /api/auth/callback** - OAuth callback (handled by PDS redirect) 77 + 3. **GET /api/auth/session** - Check current session 78 + 4. **GET /api/auth/logout** - Logout and revoke tokens 79 + 80 + **Note:** OAuth login requires a browser flow. Use the Web UI for interactive testing. 81 + 82 + ### Forum Data 83 + 84 + - **GET /api/forum** - Get forum metadata 85 + - **GET /api/categories** - List all categories 86 + - **GET /api/topics/:id** - Get topic with replies 87 + - **POST /api/topics** - Create new topic (requires auth) 88 + - **POST /api/posts** - Create reply (requires auth) 89 + 90 + ## Testing Authenticated Endpoints 91 + 92 + Authenticated endpoints (`POST /api/topics`, `POST /api/posts`) require a valid session cookie (`atbb_session`). Bruno's cookie jar will automatically store and send cookies. 93 + 94 + **To test authenticated endpoints:** 95 + 96 + 1. Use your browser to log in via the Web UI at `http://localhost:3001` 97 + 2. Use browser dev tools to copy the `atbb_session` cookie value 98 + 3. In Bruno, go to the collection settings and add a header: 99 + ``` 100 + Cookie: atbb_session=<your-cookie-value> 101 + ``` 102 + 103 + Or use Bruno's cookie management to manually set the cookie. 104 + 105 + ## Environment Variables Reference 106 + 107 + | Variable | Description | Example | 108 + |----------|-------------|---------| 109 + | `appview_url` | AppView API base URL | `http://localhost:3000` | 110 + | `web_url` | Web UI base URL | `http://localhost:3001` | 111 + | `forum_did` | Forum DID | `did:plc:abc123...` | 112 + | `user_handle` | Your AT Protocol handle | `user.bsky.social` | 113 + | `topic_id` | Valid topic ID for testing | `1` | 114 + 115 + ## Bruno Features Used 116 + 117 + - **Environments** - Switch between local/dev with one click 118 + - **Variables** - `{{variable}}` syntax for reusable values 119 + - **Assertions** - Automated response validation 120 + - **Documentation** - Inline docs for each endpoint 121 + - **Request chaining** - Variables can be set from responses (not used yet, but available) 122 + 123 + ## Tips 124 + 125 + - Use the **Environments dropdown** in Bruno to switch between local and dev 126 + - Each request includes a **Docs** tab explaining the endpoint 127 + - **Assertions** will automatically validate responses (green checkmark = passed) 128 + - Browse requests by category in the left sidebar 129 + - Requests are plain text files — they're versioned in git! 130 + 131 + ## Adding New Endpoints 132 + 133 + When you add new API routes: 134 + 135 + 1. Create a new `.bru` file in the appropriate folder 136 + 2. Follow the existing format (meta, get/post, headers, body, docs) 137 + 3. Use environment variables (`{{appview_url}}`) for URLs 138 + 4. Add assertions to validate responses 139 + 5. Commit the file — it's part of your API documentation! 140 + 141 + ## Resources 142 + 143 + - [Bruno Documentation](https://docs.usebruno.com/) 144 + - [AT Protocol Docs](https://atproto.com/) 145 + - [atBB Project Plan](../docs/atproto-forum-plan.md)
+5
bruno/bruno.json
··· 1 + { 2 + "version": "1", 3 + "name": "atBB", 4 + "type": "collection" 5 + }
+7
bruno/environments/dev.bru
··· 1 + vars { 2 + appview_url: https://api-dev.atbb.space 3 + web_url: https://dev.atbb.space 4 + forum_did: did:plc:example 5 + user_handle: user.bsky.social 6 + topic_id: 1 7 + }
+7
bruno/environments/local.bru
··· 1 + vars { 2 + appview_url: http://localhost:3000 3 + web_url: http://localhost:3001 4 + forum_did: did:plc:example 5 + user_handle: user.bsky.social 6 + topic_id: 1 7 + }