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
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 + }