feat: implement 3-level boards hierarchy (ATB-23) (#32)
* docs: design for boards hierarchy restructuring (ATB-23)
- Restructure from 2-level to 3-level traditional BB hierarchy
- Categories become groupings (non-postable)
- Boards become postable areas (new concept)
- Posts link to both boards and forums
- Includes lexicon, schema, API, and indexer changes
- No migration needed (no production data)
* feat(lexicon): add space.atbb.forum.board lexicon
- Board record type with category reference
- Owned by Forum DID, key: tid
- Required: name, category, createdAt
- Optional: description, slug, sortOrder
* feat(lexicon): add board reference to post lexicon
- Posts can now reference boards via boardRef
- Optional field for backwards compatibility
- Keeps forum reference for client flexibility
* feat(db): add boards table for 3-level hierarchy
- Boards belong to categories (categoryId FK)
- Store category URI for out-of-order indexing
- Unique index on (did, rkey) for AT Proto records
- Index on category_id for efficient filtering
- Generated migration with drizzle-kit
* feat(db): add board columns to posts table
- Add board_uri and board_id columns (nullable)
- Indexes for efficient board filtering
- Keeps forum_uri for client flexibility
* feat(indexer): add helper methods for board/category URI lookup
- getBoardIdByUri: resolve board AT URI to database ID
- getCategoryIdByUri: resolve category AT URI to database ID
- Both return null for non-existent records
- Add comprehensive tests using real database
* feat(indexer): add board indexing handlers
- handleBoardCreate/Update/Delete methods
- Resolves category URI to ID before insert
- Skips insert if category not found (logs warning)
- Tests verify indexing and orphan handling
* feat(appview): extract boardUri and boardId in post indexer
- Add test for post creation with board reference
- Update postConfig.toInsertValues to extract boardUri and look up boardId
- Update postConfig.toUpdateValues to extract boardUri
- Board references are optional (gracefully handled with null)
- Uses getBoardIdByUri helper method for FK lookup
Completes Task 7 of ATB-23 boards hierarchy implementation.
* feat(firehose): register board collection handlers
- Firehose now subscribes to space.atbb.forum.board
- Board create/update/delete events route to indexer
* feat(helpers): add getBoardByUri and serializeBoard helpers
- Add getBoardByUri: looks up board by AT-URI, returns CID or null
- Add serializeBoard: converts BigInt → string, Date → ISO string
- Add BoardRow type export for type safety
- Update test-context to clean up boards in afterEach
- Add comprehensive tests for both helpers (4 tests total)
Task 9 of ATB-23 boards hierarchy implementation
* feat(api): add GET /api/boards endpoint
- Returns all boards sorted by category, then board sortOrder
- Defensive 1000 limit to prevent memory exhaustion
- Error handling with structured logging
- Comprehensive test coverage (success, empty, database error cases)
* fix: remove internal fields from serializeBoard output
Align serializeBoard with serializeCategory and serializeForum by
hiding internal AT Protocol fields (rkey, cid) from API responses.
Changes:
- Remove rkey and cid from serializeBoard return value
- Update JSDoc comment to reflect correct response shape
- Add comprehensive serialization tests to boards.test.ts:
- Verify BigInt fields are stringified
- Verify date fields are ISO 8601 strings
- Verify internal fields (rkey, cid) are not exposed
- Verify null optional fields are handled gracefully
- Update helpers-boards.test.ts to match new serialization behavior
All 295 tests pass.
* feat(api): register boards routes in app
- GET /api/boards now accessible
- Follows existing route registration pattern
* fix(api): add stub boards route for test consistency
* feat(api): require boardUri in POST /api/topics
- boardUri is now required (returns 400 if missing)
- Validates board exists before writing to PDS
- Writes both forum and board references to post record
- forumUri always uses configured singleton
- Updated all existing tests to include boardUri
- Removed obsolete custom forumUri test
* feat(api): add GET /api/boards/:id/topics endpoint
- Returns topics (posts with NULL rootPostId) for a board
- Sorted by createdAt DESC (newest first)
- Filters out deleted posts
- Defensive 1000 limit
- Add parseBigIntParam validation for board ID
- Update test context cleanup to include topicsuser DID pattern
- Add posts deletion in boards test beforeEach for test isolation
* feat(api): add GET /api/categories/:id/boards endpoint
- Returns boards for a specific category
- Sorted by board sortOrder
- Defensive 1000 limit
* docs(bruno): add board endpoints and update topic creation
- List All Boards: GET /api/boards
- Get Board Topics: GET /api/boards/:id/topics
- Get Category Boards: GET /api/categories/:id/boards
- Update Create Topic to require boardUri
- Add board_rkey environment variable
* docs: mark ATB-23 complete in project plan
- Boards hierarchy implemented and tested
- All API endpoints functional
- Bruno collections updated
* fix: address PR #32 review feedback (tasks 1-5, 7-9)
Implements code review fixes for boards hierarchy PR:
- Add boardUri/boardId fields to serializePost response (task #1)
- Fix silent failures in indexer FK lookups - now throw errors with
structured logging instead of returning null (task #2)
- Add 404 existence checks to GET /api/boards/:id/topics and
GET /api/categories/:id/boards before querying data (task #3)
- Add comprehensive boardUri validation: type guard, format check,
collection type validation, and ownership verification (task #4)
- Add error logging to getBoardIdByUri and getCategoryIdByUri
helper functions with structured context (task #5)
- Remove redundant catch blocks from helpers - let errors bubble
to route handlers for proper classification (task #7)
- Fix migration file references in project plan document (task #8)
- Fix Bruno API documentation inaccuracies - add missing fields,
remove non-existent fields, document 404 errors (task #9)
Test infrastructure improvements:
- Fix test-context.ts forum insertion order - cleanDatabase now
runs before forum insert to prevent deletion of test data
- Update test expectations for new error behavior:
* indexer now throws on missing FK instead of silent skip
* endpoints now return 404 for non-existent resources
- All 304 tests passing
Remaining tasks: #6 (add 11 test cases), #10 (reclassify db errors)
* test: add missing test cases for boards and topics (task #6)
Adds comprehensive test coverage for board indexer operations and
boardUri validation:
Indexer tests (indexer-boards.test.ts):
- handleBoardUpdate: verifies board updates are indexed correctly
- handleBoardDelete: verifies board deletion removes record
Topics API tests (topics.test.ts):
- Malformed boardUri: returns 400 for invalid AT URI format
- Wrong collection type: returns 400 when boardUri points to category
- Wrong forum DID: returns 400 when boardUri belongs to different forum
All 309 tests passing (5 new tests added).
* fix: reclassify database connection errors as 503 (task #10)
Distinguishes database connection failures from unexpected errors:
Error classification hierarchy:
1. Programming errors (TypeError, ReferenceError) → throw to global handler
2. Network errors (PDS fetch failures, timeouts) → 503 with PDS message
3. Database errors (connection, pool, postgres) → 503 with DB message
4. Unexpected errors (validation, logic bugs) → 500 with "report issue"
Changes:
- Add isDatabaseError() helper to detect DB connection failures
- Update POST /api/topics error handling to check for DB errors
- Return 503 "Database temporarily unavailable" for connection issues
- Update 500 message to "report this issue if it persists"
- Add test for database connection errors returning 503
- Update test for true 500 errors (non-network, non-DB)
All 310 tests passing (1 new test added).