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.

docs: reorganize completed plans, trim CLAUDE.md, consolidate Bruno (#66)

* docs: reorganize completed plans and sync atproto-forum-plan

- Move all 45 completed plan docs from docs/plans/ to docs/plans/complete/
to distinguish finished work from active/upcoming plans
- Mark SQLite support as complete in the Future Roadmap section
- Add show-handles-in-posts as a completed Phase 4 item
- Update docs/plans/ path references to docs/plans/complete/

* docs: trim CLAUDE.md to gotchas-only, add CONTRIBUTING.md

CLAUDE.md's stated purpose is "common mistakes and confusion points."
Several sections had grown into general style guides and process docs
that don't serve that purpose:

- Remove generic testing workflow, quality standards, coverage expectations
→ moved to CONTRIBUTING.md
- Remove defensive programming boilerplate, global error handler template,
error testing examples → discoverable from existing code
- Remove security test coverage requirements and security checklist
→ moved to CONTRIBUTING.md
- Remove Bruno file template and testing workflow → moved to CONTRIBUTING.md

Bug fixes in CLAUDE.md:
- Fix stale `role.permissions.includes()` example → role_permissions join table
- Add `forum.board` to lexicon record ownership list (added by ATB-23)
- Add docs/plans/complete/ convention

CONTRIBUTING.md is a new contributor-facing document covering testing
workflow, error handling patterns (with examples), security checklist,
and Bruno template — content humans read before their first PR.

* docs: consolidate Bruno collections into top-level bruno/

apps/appview/bruno/ was a leftover from when the admin role endpoints
were first written. The top-level bruno/ collection was created later
but never picked up the three files that already existed:

- Admin/Assign Role.bru
- Admin/List Members.bru
- Admin/List Roles.bru

Move all three into bruno/AppView API/Admin/ alongside the backfill
endpoints. Remove the now-redundant apps/appview/bruno/bruno.json and
environments/local.bru.

Add missing environment variables (target_user_did, role_rkey) to both
local.bru and dev.bru so Assign Role.bru resolves correctly.

authored by

Malpercio and committed by
GitHub
5c8511d3 10327306

+298 -360
+26 -337
CLAUDE.md
··· 55 55 56 56 **When adding new env vars to tests:** Update `turbo.json` immediately, or tests will mysteriously fail when run via Turbo but pass when run directly. 57 57 58 - ### When to Run Tests 59 - 60 - **Before every commit:** 61 - ```sh 62 - pnpm test # Verify all tests pass 63 - git add . 64 - git commit -m "feat: your changes" 65 - ``` 66 - 67 - **Before requesting code review:** 68 - ```sh 69 - pnpm build # Ensure clean build 70 - pnpm test # Verify all tests pass 71 - # Only then push and request review 72 - ``` 73 - 74 - **After fixing review feedback:** 75 - ```sh 76 - # Make fixes 77 - pnpm test # Verify tests still pass 78 - # Push updates 79 - ``` 80 - 81 - ### Test Requirements 82 - 83 - **All new features must include tests:** 84 - - API endpoints: Test success cases, error cases, edge cases 85 - - Business logic: Test all code paths and error conditions 86 - - Error handling: Test that errors are caught and logged appropriately 87 - - Security features: Test authentication, authorization, input validation 88 - 89 - **Test quality standards:** 90 - - Tests must be independent (no shared state between tests) 91 - - Use descriptive test names that explain what is being tested 92 - - Mock external dependencies (databases, APIs, network calls) 93 - - Test error paths, not just happy paths 94 - - Verify logging and error messages are correct 95 - 96 - **Red flags (do not commit):** 97 - - Skipped tests (`test.skip`, `it.skip`) without Linear issue tracking why 98 - - Tests that pass locally but fail in CI 99 - - Tests that require manual setup or specific data 100 - - Tests with hardcoded timing (`setTimeout`, `sleep`) - use proper mocks 101 - - Placeholder/stub tests that don't actually test anything 102 - 103 58 **Placeholder tests are prohibited:** 104 59 ```typescript 105 60 // ❌ FORBIDDEN: Stub tests provide false confidence ··· 129 84 }); 130 85 ``` 131 86 132 - **Why this matters:** Stub tests pass in CI, creating false confidence that code is tested. They hide bugs that would be caught by real tests with actual assertions. 133 - 134 - **If you're unsure how to test something:** Leave a `// TODO: Add test for X` comment and create a Linear issue. Never commit a stub test that pretends to test something. 135 - 136 - 137 - ### Test Coverage Expectations 138 - 139 - While we don't enforce strict coverage percentages, aim for: 140 - - **Critical paths:** 100% coverage (authentication, authorization, data integrity) 141 - - **Error handling:** All catch blocks should be tested 142 - - **API endpoints:** All routes should have tests 143 - - **Business logic:** All functions with branching logic should be tested 144 - 145 - **Do not:** 146 - - Skip writing tests to "move faster" - untested code breaks in production 147 - - Write tests after requesting review - tests inform implementation 148 - - Rely on manual testing alone - automated tests catch regressions 87 + **Skipped tests (`test.skip`, `it.skip`) must have a Linear issue tracking why.** Skipping without a tracked reason is prohibited. 149 88 150 89 ### Before Requesting Code Review 151 90 ··· 178 117 - **References:** Use `com.atproto.repo.strongRef` wrapped in named defs (e.g., `forumRef`, `subjectRef`). 179 118 - **Extensible fields:** Use `knownValues` (not `enum`) for strings that may grow (permissions, reaction types, mod actions). 180 119 - **Record ownership:** 181 - - Forum DID owns: `forum.forum`, `forum.category`, `forum.role`, `modAction` 120 + - Forum DID owns: `forum.forum`, `forum.category`, `forum.board`, `forum.role`, `modAction` 182 121 - User DID owns: `post`, `membership`, `reaction` 183 122 184 123 ## AT Protocol Conventions ··· 455 394 - Prefer returning `null` over fake values (`"0"`, `new Date()`) 456 395 - Document fallback behavior in JSDoc if unavoidable 457 396 458 - ### Defensive Programming 459 - 460 - **All list queries must have defensive limits:** 461 - ```typescript 462 - .from(categories) 463 - .orderBy(categories.sortOrder) 464 - .limit(1000); // Prevent memory exhaustion on unbounded queries 465 - ``` 466 - 467 - **Filter deleted/soft-deleted records:** 468 - ```typescript 469 - .where(and( 470 - eq(posts.rootPostId, topicId), 471 - eq(posts.deleted, false) // Never show deleted content to users 472 - )) 473 - ``` 474 - 475 - **Use ordering for consistent results:** 476 - ```typescript 477 - .orderBy(asc(posts.createdAt)) // Chronological order for replies 478 - ``` 479 - 480 - ### Global Error Handler 481 - 482 - The Hono app must have a global error handler as a safety net: 483 - ```typescript 484 - app.onError((err, c) => { 485 - console.error("Unhandled error in route handler", { 486 - path: c.req.path, 487 - method: c.req.method, 488 - error: err.message, 489 - stack: err.stack, 490 - }); 491 - return c.json( 492 - { 493 - error: "An internal error occurred. Please try again later.", 494 - ...(process.env.NODE_ENV !== "production" && { 495 - details: err.message, 496 - }), 497 - }, 498 - 500 499 - ); 500 - }); 501 - ``` 502 - 503 - ### Testing Error Handling 504 - 505 - **Test error classification, not just error catching.** Users need actionable feedback: "retry later" (503) vs "report this bug" (500). 506 - 507 - ```typescript 508 - // ✅ Test network errors return 503 (retry later) 509 - it("returns 503 when PDS connection fails", async () => { 510 - mockPutRecord.mockRejectedValueOnce(new Error("fetch failed")); 511 - const res = await app.request("/api/topics", { 512 - method: "POST", 513 - body: JSON.stringify({ text: "Test" }) 514 - }); 515 - expect(res.status).toBe(503); // Not 500! 516 - const data = await res.json(); 517 - expect(data.error).toContain("Unable to reach your PDS"); 518 - }); 519 - 520 - // ✅ Test server errors return 500 (bug report) 521 - it("returns 500 for unexpected database errors", async () => { 522 - mockPutRecord.mockRejectedValueOnce(new Error("Database connection lost")); 523 - const res = await app.request("/api/topics", { 524 - method: "POST", 525 - body: JSON.stringify({ text: "Test" }) 526 - }); 527 - expect(res.status).toBe(500); // Not 503! 528 - const data = await res.json(); 529 - expect(data.error).not.toContain("PDS"); // Generic message for server errors 530 - }); 531 - 532 - // ✅ Test input validation returns 400 533 - it("returns 400 for malformed JSON", async () => { 534 - const res = await app.request("/api/topics", { 535 - method: "POST", 536 - headers: { "Content-Type": "application/json" }, 537 - body: "{ invalid json }" 538 - }); 539 - expect(res.status).toBe(400); 540 - const data = await res.json(); 541 - expect(data.error).toContain("Invalid JSON"); 542 - }); 543 - ``` 544 - 545 - **Error classification patterns to test:** 546 - - **400 (Bad Request):** Invalid input, missing required fields, malformed JSON 547 - - **404 (Not Found):** Resource doesn't exist (forum, post, user) 548 - - **503 (Service Unavailable):** Network errors, PDS connection failures, timeouts — user should retry 549 - - **500 (Internal Server Error):** Unexpected errors, database errors — needs bug investigation 550 - 551 397 ## Security-Critical Code Standards 552 398 553 399 When implementing authentication, authorization, or permission systems, follow these additional requirements: ··· 574 420 return false; // Role deleted = deny access 575 421 } 576 422 577 - return role.permissions.includes(permission) || 578 - role.permissions.includes("*"); 423 + // Permissions live in role_permissions join table, not role.permissions array 424 + const [match] = await ctx.db 425 + .select() 426 + .from(rolePermissions) 427 + .where(and( 428 + eq(rolePermissions.roleId, role.id), 429 + or( 430 + eq(rolePermissions.permission, permission), 431 + eq(rolePermissions.permission, "*") 432 + ) 433 + )) 434 + .limit(1); 435 + 436 + return !!match; 579 437 } catch (error) { 580 438 if (isProgrammingError(error)) throw error; 581 439 582 440 console.error("Failed to check permissions - denying access", { 583 - did, permission, error: ... 441 + did, permission, error: String(error) 584 442 }); 585 443 return false; // Error = deny access (fail closed) 586 444 } ··· 608 466 }); 609 467 ``` 610 468 611 - ### 2. Security Test Coverage Requirements 612 - 613 - **All security features require comprehensive tests covering:** 614 - 615 - - ✅ **Happy path** - Authorized user succeeds 616 - - ✅ **Unauthorized user** - Returns 401 (not authenticated) 617 - - ✅ **Forbidden** - Returns 403 (authenticated but lacks permission) 618 - - ✅ **Privilege escalation prevention** - Cannot grant yourself higher privileges 619 - - ✅ **Peer protection** - Cannot modify users with equal authority 620 - - ✅ **Fail-closed behavior** - Database/network errors deny access 621 - - ✅ **Input validation** - Malformed requests return 400, not 500 622 - - ✅ **Error classification** - Network errors (503) vs server errors (500) 623 - 624 - **Example security test suite structure:** 625 - ```typescript 626 - describe("POST /api/admin/members/:did/role (security-critical)", () => { 627 - describe("Authorization", () => { 628 - it("returns 401 when not authenticated", async () => { /* ... */ }); 629 - it("returns 403 when user lacks manageRoles permission", async () => { /* ... */ }); 630 - }); 631 - 632 - describe("Privilege Escalation Prevention", () => { 633 - it("prevents admin from assigning owner role (higher authority)", async () => { 634 - // Admin (priority 10) tries to assign Owner (priority 0) → 403 635 - }); 636 - 637 - it("prevents admin from assigning admin role (equal authority)", async () => { 638 - // Admin (priority 10) tries to assign Admin (priority 10) → 403 639 - }); 640 - 641 - it("allows admin to assign moderator role (lower authority)", async () => { 642 - // Admin (priority 10) assigns Moderator (priority 20) → 200 643 - }); 644 - }); 645 - 646 - describe("Error Handling", () => { 647 - it("returns 503 when PDS connection fails (network error)", async () => { /* ... */ }); 648 - it("returns 500 when database query fails (server error)", async () => { /* ... */ }); 649 - it("returns 400 for malformed roleUri (input validation)", async () => { /* ... */ }); 650 - }); 651 - }); 652 - ``` 653 - 654 - ### 3. Startup Failures for Missing Security Infrastructure 469 + ### 2. Startup Failures for Missing Security Infrastructure 655 470 656 471 **Security-critical infrastructure must fail fast on startup, not at first request.** 657 472 ··· 680 495 681 496 **Why this matters:** If the permission system is broken, every request will fail authorization. It's better to fail startup loudly than silently deploy a non-functional system. 682 497 683 - ### 4. Security Code Review Checklist 684 - 685 - Before requesting review for authentication/authorization code, verify: 686 - 687 - - [ ] All permission checks fail closed (deny access on error) 688 - - [ ] Database errors in security checks are caught and logged 689 - - [ ] Programming errors (TypeError) are re-thrown, not caught 690 - - [ ] Privilege escalation is prevented (equal/higher authority blocked) 691 - - [ ] Tests cover unauthorized (401), forbidden (403), and error cases 692 - - [ ] Error messages don't leak internal details (priority values, permission names) 693 - - [ ] Middleware composition is correct (auth before permission checks) 694 - - [ ] Startup fails fast if security infrastructure is unavailable 695 - 696 498 ## Documentation & Project Tracking 697 499 698 500 **Keep these synchronized when completing work:** ··· 707 509 - Add comments documenting implementation details when marking Done 708 510 - Keep status in sync with actual codebase state, not planning estimates 709 511 710 - 3. **Workflow:** When finishing a task: 512 + 3. **`docs/plans/` convention:** 513 + - Active/in-progress plan documents go directly in `docs/plans/` 514 + - Completed plan documents move to `docs/plans/complete/` when work is shipped 515 + 516 + 4. **Workflow:** When finishing a task: 711 517 ```sh 712 518 # 1. Run tests to verify implementation is correct 713 519 pnpm test ··· 726 532 727 533 ## Bruno API Collections 728 534 729 - **CRITICAL: Keep Bruno collections synchronized with API changes.** 535 + **CRITICAL: Update Bruno collections in the SAME commit as route implementation.** 730 536 731 - The `bruno/` directory contains [Bruno](https://www.usebruno.com/) collections that serve dual purpose: 732 - 1. **Interactive API testing** during development 733 - 2. **Version-controlled API documentation** that stays in sync with code 537 + The `bruno/` directory contains [Bruno](https://www.usebruno.com/) collections for API documentation and interactive testing. See `CONTRIBUTING.md` for the full update workflow and file template. 734 538 735 - ### When to Update Bruno Collections 736 - 737 - **When adding a new API endpoint:** 738 - 1. Create a new `.bru` file in the appropriate `bruno/AppView API/` subdirectory 739 - 2. Follow the naming pattern: use descriptive names like `Create Topic.bru`, `Get Forum Metadata.bru` 740 - 3. Include all request details: method, URL with variables, headers, body (if POST/PUT) 741 - 4. Add comprehensive documentation in the `docs` block explaining: 742 - - Required/optional parameters 743 - - Expected response format with example 744 - - All possible error codes (400, 401, 404, 500, 503) 745 - - Authentication requirements 746 - - Validation rules 747 - 5. Add assertions to validate responses automatically 748 - 749 - **When modifying an existing endpoint:** 750 - 1. Update the corresponding `.bru` file in `bruno/AppView API/` 751 - 2. Update parameter descriptions if inputs changed 752 - 3. Update response documentation if output format changed 753 - 4. Update error documentation if new error cases added 754 - 5. Update assertions if validation logic changed 755 - 756 - **When removing an endpoint:** 757 - 1. Delete the corresponding `.bru` file 758 - 2. Update `bruno/README.md` if it referenced the removed endpoint 759 - 760 - **When adding new environment variables:** 761 - 1. Update `bruno/environments/local.bru` with local development values 762 - 2. Update `bruno/environments/dev.bru` with deployment values 763 - 3. Document the variable in `bruno/README.md` under "Environment Variables Reference" 764 - 765 - ### Bruno File Template 766 - 767 - When creating new `.bru` files, follow this template: 768 - 769 - ```bru 770 - meta { 771 - name: Endpoint Name 772 - type: http 773 - seq: 1 774 - } 775 - 776 - get { 777 - url: {{appview_url}}/api/path 778 - } 779 - 780 - params:query { 781 - param1: {{variable}} 782 - } 783 - 784 - headers { 785 - Content-Type: application/json 786 - } 787 - 788 - body:json { 789 - { 790 - "field": "value" 791 - } 792 - } 793 - 794 - assert { 795 - res.status: eq 200 796 - res.body.field: isDefined 797 - } 798 - 799 - docs { 800 - Brief description of what this endpoint does. 801 - 802 - Path/query/body params: 803 - - param1: Description (type, required/optional) 804 - 805 - Returns: 806 - { 807 - "field": "value" 808 - } 809 - 810 - Error codes: 811 - - 400: Bad request (invalid input) 812 - - 401: Unauthorized (requires auth) 813 - - 404: Not found 814 - - 500: Server error 815 - 816 - Notes: 817 - - Any special considerations or validation rules 818 - } 819 - ``` 820 - 821 - ### Workflow Integration 822 - 823 - **When committing API changes, update Bruno collections in the SAME commit:** 824 - 825 - ```sh 826 - # Example: Adding a new endpoint 827 - git add apps/appview/src/routes/my-route.ts 828 - git add apps/appview/src/routes/__tests__/my-route.test.ts 829 - git add bruno/AppView\ API/MyRoute/New\ Endpoint.bru 830 - git commit -m "feat: add new endpoint for X 831 - 832 - - Implements POST /api/my-endpoint 833 - - Adds validation for Y 834 - - Updates Bruno collection with request documentation" 835 - ``` 836 - 837 - **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. 838 - 839 - ### Testing Bruno Collections 840 - 841 - Before committing: 842 - 1. Open the collection in Bruno 843 - 2. Test each modified request against your local dev server (`pnpm dev`) 844 - 3. Verify assertions pass (green checkmarks) 845 - 4. Verify documentation is accurate and complete 846 - 5. Check that error scenarios are documented (not just happy path) 847 - 848 - ### Common Mistakes 539 + **Common Mistakes:** 849 540 850 541 **DON'T:** 851 542 - Commit API changes without updating Bruno collections ··· 858 549 - Update Bruno files in the same commit as route implementation 859 550 - Use environment variables for all URLs and test data 860 551 - Document all HTTP status codes the endpoint can return 861 - - Include example request/response bodies that match actual behavior 862 - - Test requests locally before committing 863 552 864 553 ## Git Conventions 865 554
+264
CONTRIBUTING.md
··· 1 + # Contributing to atBB 2 + 3 + This document covers development workflow, testing standards, and coding patterns for contributors. For project-specific gotchas and non-obvious quirks, see [`CLAUDE.md`](CLAUDE.md). 4 + 5 + ## Testing Workflow 6 + 7 + ### When to Run Tests 8 + 9 + **Before every commit:** 10 + ```sh 11 + pnpm test 12 + git add <specific files> 13 + git commit -m "feat: your changes" 14 + ``` 15 + 16 + **Before requesting code review:** 17 + ```sh 18 + pnpm build # Ensure clean build 19 + pnpm test # Verify all tests pass 20 + # Only then push and request review 21 + ``` 22 + 23 + **After fixing review feedback:** 24 + ```sh 25 + # Make fixes 26 + pnpm test # Verify tests still pass 27 + # Push updates 28 + ``` 29 + 30 + ### Test Requirements 31 + 32 + All new features must include tests: 33 + - **API endpoints:** Success cases, error cases, edge cases 34 + - **Business logic:** All code paths and error conditions 35 + - **Error handling:** All catch blocks tested 36 + - **Security features:** Authentication, authorization, input validation 37 + 38 + ### Test Quality Standards 39 + 40 + - Tests must be independent (no shared state between tests) 41 + - Use descriptive test names that explain what is being tested 42 + - Mock external dependencies (databases, APIs, network calls) 43 + - Test error paths, not just happy paths 44 + - Verify logging and error messages are correct 45 + 46 + ### Test Coverage Expectations 47 + 48 + While we don't enforce strict coverage percentages, aim for: 49 + - **Critical paths:** 100% coverage (authentication, authorization, data integrity) 50 + - **Error handling:** All catch blocks should be tested 51 + - **API endpoints:** All routes should have tests 52 + - **Business logic:** All functions with branching logic should be tested 53 + 54 + Do not: 55 + - Skip writing tests to "move faster" — untested code breaks in production 56 + - Write tests after requesting review — tests inform implementation 57 + - Rely on manual testing alone — automated tests catch regressions 58 + 59 + ## Error Handling Patterns 60 + 61 + ### Defensive Programming 62 + 63 + All list queries must include defensive limits: 64 + ```typescript 65 + .from(categories) 66 + .orderBy(categories.sortOrder) 67 + .limit(1000); // Prevent memory exhaustion on unbounded queries 68 + ``` 69 + 70 + Always filter soft-deleted records in read queries: 71 + ```typescript 72 + .where(and( 73 + eq(posts.rootPostId, topicId), 74 + eq(posts.deleted, false), 75 + eq(posts.bannedByMod, false) 76 + )) 77 + ``` 78 + 79 + Use explicit ordering for consistent, reproducible results: 80 + ```typescript 81 + .orderBy(asc(posts.createdAt)) // Chronological order for replies 82 + ``` 83 + 84 + ### Global Error Handler 85 + 86 + Every Hono app must have a global error handler as a safety net. See `apps/appview/src/create-app.ts` for the production implementation: 87 + 88 + ```typescript 89 + app.onError((err, c) => { 90 + console.error("Unhandled error in route handler", { 91 + path: c.req.path, 92 + method: c.req.method, 93 + error: err.message, 94 + stack: err.stack, 95 + }); 96 + return c.json( 97 + { 98 + error: "An internal error occurred. Please try again later.", 99 + ...(process.env.NODE_ENV !== "production" && { 100 + details: err.message, 101 + }), 102 + }, 103 + 500 104 + ); 105 + }); 106 + ``` 107 + 108 + ### Testing Error Classification 109 + 110 + Test that errors return the right status code — users need actionable feedback: 111 + 112 + | Status | When to return | 113 + |--------|----------------| 114 + | 400 | Invalid input, missing required fields, malformed JSON | 115 + | 404 | Resource doesn't exist | 116 + | 503 | Network errors, PDS connection failures — user should retry | 117 + | 500 | Unexpected errors, database errors — needs investigation | 118 + 119 + ```typescript 120 + // ✅ Network errors should return 503 (user should retry) 121 + it("returns 503 when PDS connection fails", async () => { 122 + mockPutRecord.mockRejectedValueOnce(new Error("fetch failed")); 123 + const res = await app.request("/api/topics", { 124 + method: "POST", 125 + body: JSON.stringify({ text: "Test" }) 126 + }); 127 + expect(res.status).toBe(503); 128 + }); 129 + 130 + // ✅ Server errors should return 500 (needs investigation) 131 + it("returns 500 for unexpected database errors", async () => { 132 + mockPutRecord.mockRejectedValueOnce(new Error("Database connection lost")); 133 + const res = await app.request("/api/topics", { 134 + method: "POST", 135 + body: JSON.stringify({ text: "Test" }) 136 + }); 137 + expect(res.status).toBe(500); 138 + }); 139 + 140 + // ✅ Input validation should return 400 141 + it("returns 400 for malformed JSON", async () => { 142 + const res = await app.request("/api/topics", { 143 + method: "POST", 144 + headers: { "Content-Type": "application/json" }, 145 + body: "{ invalid json }" 146 + }); 147 + expect(res.status).toBe(400); 148 + }); 149 + ``` 150 + 151 + ## Security Code Review Checklist 152 + 153 + Before requesting review for authentication/authorization code: 154 + 155 + - [ ] All permission checks fail closed (deny access on error) 156 + - [ ] Database errors in security checks are caught and logged 157 + - [ ] Programming errors (TypeError) are re-thrown, not caught 158 + - [ ] Privilege escalation is prevented (equal/higher authority blocked) 159 + - [ ] Tests cover unauthorized (401), forbidden (403), and error cases 160 + - [ ] Error messages don't leak internal details (priority values, permission names) 161 + - [ ] Middleware composition is correct (auth before permission checks) 162 + - [ ] Startup fails fast if security infrastructure is unavailable 163 + 164 + **Example test suite structure for security-critical endpoints:** 165 + ```typescript 166 + describe("POST /api/admin/members/:did/role (security-critical)", () => { 167 + describe("Authorization", () => { 168 + it("returns 401 when not authenticated", async () => { /* ... */ }); 169 + it("returns 403 when user lacks manageRoles permission", async () => { /* ... */ }); 170 + }); 171 + 172 + describe("Privilege Escalation Prevention", () => { 173 + it("prevents admin from assigning owner role (higher authority)", async () => { 174 + // Admin (priority 10) tries to assign Owner (priority 0) → 403 175 + }); 176 + it("allows admin to assign moderator role (lower authority)", async () => { 177 + // Admin (priority 10) assigns Moderator (priority 20) → 200 178 + }); 179 + }); 180 + 181 + describe("Error Handling", () => { 182 + it("returns 503 when PDS connection fails (network error)", async () => { /* ... */ }); 183 + it("returns 500 when database query fails (server error)", async () => { /* ... */ }); 184 + it("returns 400 for malformed roleUri (input validation)", async () => { /* ... */ }); 185 + }); 186 + }); 187 + ``` 188 + 189 + ## Bruno API Collections 190 + 191 + The `bruno/` directory contains [Bruno](https://www.usebruno.com/) collections for API documentation and interactive testing. 192 + 193 + ### When to Update 194 + 195 + | Event | Action | 196 + |-------|--------| 197 + | New endpoint | Create a `.bru` file in `bruno/AppView API/<feature>/` | 198 + | Modified endpoint | Update the corresponding `.bru` file | 199 + | Removed endpoint | Delete the `.bru` file; update `bruno/README.md` if referenced | 200 + | New environment variable | Update `bruno/environments/local.bru` and `bruno/environments/dev.bru`; document in `bruno/README.md` | 201 + 202 + Always update Bruno files **in the same commit** as the route implementation. 203 + 204 + ### File Template 205 + 206 + ```bru 207 + meta { 208 + name: Endpoint Name 209 + type: http 210 + seq: 1 211 + } 212 + 213 + get { 214 + url: {{appview_url}}/api/path 215 + } 216 + 217 + params:query { 218 + param1: {{variable}} 219 + } 220 + 221 + headers { 222 + Content-Type: application/json 223 + } 224 + 225 + body:json { 226 + { 227 + "field": "value" 228 + } 229 + } 230 + 231 + assert { 232 + res.status: eq 200 233 + res.body.field: isDefined 234 + } 235 + 236 + docs { 237 + Brief description of what this endpoint does. 238 + 239 + Path/query/body params: 240 + - param1: Description (type, required/optional) 241 + 242 + Returns: 243 + { 244 + "field": "value" 245 + } 246 + 247 + Error codes: 248 + - 400: Bad request (invalid input) 249 + - 401: Unauthorized (requires auth) 250 + - 404: Not found 251 + - 500: Server error 252 + 253 + Notes: 254 + - Any special considerations or validation rules 255 + } 256 + ``` 257 + 258 + ### Before Committing 259 + 260 + 1. Open the collection in Bruno 261 + 2. Test each modified request against your local dev server (`pnpm dev`) 262 + 3. Verify assertions pass (green checkmarks) 263 + 4. Verify documentation is accurate and complete 264 + 5. Check that error scenarios are documented (not just happy path)
apps/appview/bruno/AppView API/Admin/Assign Role.bru bruno/AppView API/Admin/Assign Role.bru
apps/appview/bruno/AppView API/Admin/List Members.bru bruno/AppView API/Admin/List Members.bru
apps/appview/bruno/AppView API/Admin/List Roles.bru bruno/AppView API/Admin/List Roles.bru
-9
apps/appview/bruno/bruno.json
··· 1 - { 2 - "version": "1", 3 - "name": "atBB AppView API", 4 - "type": "collection", 5 - "ignore": [ 6 - "node_modules", 7 - ".git" 8 - ] 9 - }
-6
apps/appview/bruno/environments/local.bru
··· 1 - vars { 2 - appview_url: http://localhost:3000 3 - forum_did: did:plc:test-forum 4 - target_user_did: did:plc:test-target 5 - role_rkey: 3xxxxxxxxx 6 - }
+2
bruno/environments/dev.bru
··· 5 5 user_handle: user.bsky.social 6 6 topic_id: 1 7 7 board_rkey: 3k2a7b 8 + target_user_did: did:plc:test-target 9 + role_rkey: 3xxxxxxxxx 8 10 }
+2
bruno/environments/local.bru
··· 5 5 user_handle: user.bsky.social 6 6 topic_id: 1 7 7 board_rkey: 3k2a7b 8 + target_user_did: did:plc:test-target 9 + role_rkey: 3xxxxxxxxx 8 10 }
+4 -8
docs/atproto-forum-plan.md
··· 265 265 - ATB-24 | Topic view mod buttons (lock/hide/ban) gated on permissions; `<dialog>` confirmation modal; `POST /mod/action` web proxy route; `getSessionWithPermissions()` for permission-aware rendering 266 266 - [x] Basic responsive design 267 267 - ATB-32 | Mobile-first responsive breakpoints (375px/768px/1024px), CSS-only hamburger nav via `<details>`/`<summary>`, token overrides for mobile, accessibility improvements (skip link, focus-visible, ARIA attributes, semantic HTML), 404 page, visual polish (transitions, hover states), SVG favicon 268 + - [x] Show author handles in posts 269 + - Design in `docs/plans/complete/2026-02-24-show-handles-in-posts-design.md`. OAuth callback upserts `users.handle` after `getProfile()` resolves; posts display human-readable handles instead of raw DIDs. Users who have never logged in still show their DID (intentional MVP scope). No schema changes required. 268 270 269 271 #### Phase 5: Packaging & Deployment (Week 9–10) 270 272 - [x] Dockerfiles for AppView and Web UI — **Complete:** Multi-stage Dockerfile with Node 22 Alpine, nginx reverse proxy, health checks (ATB-28) ··· 313 315 314 316 React Native + Expo cross-platform apps consuming the same `/api/*` endpoints as the web UI. Phased rollout: read-only browse → write/interact → push notifications → offline support & app store release. Full plan in [`docs/mobile-apps-plan.md`](mobile-apps-plan.md). 315 317 316 - ### SQLite Support 318 + ### ~~SQLite Support~~ ✅ Complete 317 319 318 - Design approved 2026-02-24 (`docs/plans/2026-02-24-sqlite-support-design.md`). Enables lightweight single-file deployments without a separate Postgres process: 319 - 320 - - URL-prefix detection in `createDb()` factory (`postgresql://` vs `file:`) for automatic dialect selection 321 - - Separate `schema.sqlite.ts` with integer IDs and unix timestamp column helpers 322 - - `role_permissions` join table to replace Postgres-specific array column (improves both dialects) 323 - - Separate Drizzle config files (`drizzle.postgres.config.ts`, `drizzle.sqlite.config.ts`) and migration directories per dialect 324 - - TypeScript types identical for both dialects via Drizzle `mode` specifiers 320 + Design in `docs/plans/complete/2026-02-24-sqlite-support-design.md`. URL-prefix detection in `createDb()` selects the backend automatically (`postgresql://` → postgres.js + Drizzle PG dialect; `file:` → @libsql/client + Drizzle LibSQL dialect). The `role_permissions` join table replaced the Postgres-specific `text[]` array column, improving both dialects. Separate schema files, Drizzle config files, and migration directories per dialect. TypeScript types are identical for both via Drizzle `mode` specifiers. Docker Compose variant (`docker-compose.sqlite.yml`) and NixOS module updated with `database.type` option. 325 321 326 322 ### Other Future Work 327 323 - **Setup wizard for first-time initialization** — Interactive web-based wizard for administrators to initialize a new forum instance (create forum record on PDS, configure categories, set admin roles). Currently requires manual PDS API calls.
docs/plans/2026-02-06-database-schema-design.md docs/plans/complete/2026-02-06-database-schema-design.md
docs/plans/2026-02-07-oauth-implementation-design.md docs/plans/complete/2026-02-07-oauth-implementation-design.md
docs/plans/2026-02-07-oauth-implementation.md docs/plans/complete/2026-02-07-oauth-implementation.md
docs/plans/2026-02-09-write-endpoints-design.md docs/plans/complete/2026-02-09-write-endpoints-design.md
docs/plans/2026-02-09-write-endpoints-implementation.md docs/plans/complete/2026-02-09-write-endpoints-implementation.md
docs/plans/2026-02-10-oxlint-lefthook-implementation.md docs/plans/complete/2026-02-10-oxlint-lefthook-implementation.md
docs/plans/2026-02-10-oxlint-lefthook-setup-design.md docs/plans/complete/2026-02-10-oxlint-lefthook-setup-design.md
docs/plans/2026-02-11-deployment-infrastructure-design.md docs/plans/complete/2026-02-11-deployment-infrastructure-design.md
docs/plans/2026-02-11-deployment-infrastructure-implementation.md docs/plans/complete/2026-02-11-deployment-infrastructure-implementation.md
docs/plans/2026-02-11-membership-auto-creation-design.md docs/plans/complete/2026-02-11-membership-auto-creation-design.md
docs/plans/2026-02-13-atb-18-forum-agent-design.md docs/plans/complete/2026-02-13-atb-18-forum-agent-design.md
docs/plans/2026-02-13-atb-18-forum-agent.md docs/plans/complete/2026-02-13-atb-18-forum-agent.md
docs/plans/2026-02-13-atb-23-boards-hierarchy-implementation.md docs/plans/complete/2026-02-13-atb-23-boards-hierarchy-implementation.md
docs/plans/2026-02-13-boards-hierarchy-design.md docs/plans/complete/2026-02-13-boards-hierarchy-design.md
docs/plans/2026-02-14-permissions-design.md docs/plans/complete/2026-02-14-permissions-design.md
docs/plans/2026-02-14-permissions-implementation.md docs/plans/complete/2026-02-14-permissions-implementation.md
docs/plans/2026-02-15-moderation-endpoints-design.md docs/plans/complete/2026-02-15-moderation-endpoints-design.md
docs/plans/2026-02-15-moderation-endpoints-implementation.md docs/plans/complete/2026-02-15-moderation-endpoints-implementation.md
docs/plans/2026-02-16-atb21-firehose-ban-enforcement-design.md docs/plans/complete/2026-02-16-atb21-firehose-ban-enforcement-design.md
docs/plans/2026-02-16-atb21-firehose-ban-enforcement.md docs/plans/complete/2026-02-16-atb21-firehose-ban-enforcement.md
docs/plans/2026-02-16-enforce-mod-actions-read-path.md docs/plans/complete/2026-02-16-enforce-mod-actions-read-path.md
docs/plans/2026-02-17-atb-26-web-ui-foundation.md docs/plans/complete/2026-02-17-atb-26-web-ui-foundation.md
docs/plans/2026-02-17-web-ui-foundation-design.md docs/plans/complete/2026-02-17-web-ui-foundation-design.md
docs/plans/2026-02-18-board-view-design.md docs/plans/complete/2026-02-18-board-view-design.md
docs/plans/2026-02-18-board-view-impl.md docs/plans/complete/2026-02-18-board-view-impl.md
docs/plans/2026-02-18-bootstrap-cli-design.md docs/plans/complete/2026-02-18-bootstrap-cli-design.md
docs/plans/2026-02-18-bootstrap-cli-implementation.md docs/plans/complete/2026-02-18-bootstrap-cli-implementation.md
docs/plans/2026-02-18-cli-categories-boards-design.md docs/plans/complete/2026-02-18-cli-categories-boards-design.md
docs/plans/2026-02-18-cli-categories-boards-implementation.md docs/plans/complete/2026-02-18-cli-categories-boards-implementation.md
docs/plans/2026-02-18-homepage-design.md docs/plans/complete/2026-02-18-homepage-design.md
docs/plans/2026-02-18-homepage-implementation.md docs/plans/complete/2026-02-18-homepage-implementation.md
docs/plans/2026-02-19-admin-moderation-ui-design.md docs/plans/complete/2026-02-19-admin-moderation-ui-design.md
docs/plans/2026-02-19-admin-moderation-ui.md docs/plans/complete/2026-02-19-admin-moderation-ui.md
docs/plans/2026-02-19-atb-31-compose-forms.md docs/plans/complete/2026-02-19-atb-31-compose-forms.md
docs/plans/2026-02-19-compose-forms-design.md docs/plans/complete/2026-02-19-compose-forms-design.md
docs/plans/2026-02-19-topic-view-design.md docs/plans/complete/2026-02-19-topic-view-design.md
docs/plans/2026-02-19-topic-view.md docs/plans/complete/2026-02-19-topic-view.md
docs/plans/2026-02-20-nixos-flake-design.md docs/plans/complete/2026-02-20-nixos-flake-design.md
docs/plans/2026-02-20-nixos-flake-implementation.md docs/plans/complete/2026-02-20-nixos-flake-implementation.md
docs/plans/2026-02-20-responsive-a11y-polish-design.md docs/plans/complete/2026-02-20-responsive-a11y-polish-design.md
docs/plans/2026-02-20-responsive-a11y-polish-plan.md docs/plans/complete/2026-02-20-responsive-a11y-polish-plan.md
docs/plans/2026-02-22-backfill-repo-sync-design.md docs/plans/complete/2026-02-22-backfill-repo-sync-design.md
docs/plans/2026-02-22-backfill-repo-sync-implementation.md docs/plans/complete/2026-02-22-backfill-repo-sync-implementation.md
docs/plans/2026-02-24-show-handles-in-posts-design.md docs/plans/complete/2026-02-24-show-handles-in-posts-design.md
docs/plans/2026-02-24-sqlite-support-design.md docs/plans/complete/2026-02-24-sqlite-support-design.md
docs/plans/2026-02-24-sqlite-support-plan.md docs/plans/complete/2026-02-24-sqlite-support-plan.md