Bulletin Board App
Overview
The bulletin app provides an announcement board and small community space inside a brand's LINE presence. Teams post announcements, users comment and react, and the module ships with content reporting, blocking of disruptive users, and an audit log recording every staff action.
This module was written from a design spec rather than ported from NestJS, and it enforces six data-safety rules that the code follows strictly. Understanding them is a prerequisite for changing anything here.
Business Flow
The Six Rules Enforced Across the Module
These rules are documented in the doc comment of every service file.
- Every query goes through
repo.scopedorscopedTx, which enforce bothorganization_idandline_oa_id. - Any id supplied by the caller is checked for ownership, not merely existence — and a miss returns 404, not 403, because a 403 would confirm the row exists in another organization.
- Every mutation shares a transaction with its audit log row.
- Counter columns must never be written —
comment_count,reaction_count, andreport_countare owned by database triggers. - Posts and comments must never be mutated in the same transaction. Triggers take advisory locks per target (4201 for posts, 4202 for comments), so interleaving them deadlocks.
- Deletion is always
status = 'deleted'plus adeleted_datestamp. There is no hard delete.
Settings and Categories
GET /api/apps/bulletin/settingsandPUT /api/apps/bulletin/settingsconfigure the board — whether comments are open, whether posts need approval, and so on.GET /api/apps/bulletin/pending-countreturns the number of items awaiting approval, used for the menu badge.- Categories are managed through
GET /api/apps/bulletin/categories,POST /api/apps/bulletin/categories,PUT /api/apps/bulletin/categories/:id, andDELETE /api/apps/bulletin/categories/:id.
Posts and Comments
GET /api/apps/bulletin/postsandPOST /api/apps/bulletin/postslist and create posts;GET /api/apps/bulletin/posts/:idandPUT /api/apps/bulletin/posts/:idread and edit one.PUT /api/apps/bulletin/posts/:id/statuschanges a post's status — published, hidden, or deleted.PUT /api/apps/bulletin/posts/:id/pinpins a post.PUT /api/apps/bulletin/posts/:id/comments-settingopens or closes comments on that specific post.GET /api/apps/bulletin/posts/:id/commentslists a post's comments.GET /api/apps/bulletin/commentsshows the board-wide comment queue, andPUT /api/apps/bulletin/comments/:id/statusapproves, hides, or deletes a comment.
Safety and Moderation
GET /api/apps/bulletin/reportsshows the content report queue, andPUT /api/apps/bulletin/reports/:id/statusresolves or dismisses a report.- Report rows are never deleted, because the
idx_bul_report_onceindex is what prevents the same user from reporting the same item twice.
- Report rows are never deleted, because the
GET /api/apps/bulletin/blocksandPOST /api/apps/bulletin/blockslist and create user blocks;DELETE /api/apps/bulletin/blocks/:idrevokes a block rather than deleting the row.GET /api/apps/bulletin/audit-logprovides read-only access to the staff action history.
Key Files & Functions
The code lives in internal/modules/bulletin/.
| File | Role |
|---|---|
controller.go | Route registration; every route carries apps.AppEnabledGuard(d) |
scope.go | repo.scoped and scopedTx, which enforce tenant scoping |
service_board.go | Settings, categories, pending-count |
service_moderation.go | Posts, comments, status transitions, pinning |
service_safety.go | Reports, blocks, audit log reads |
audit.go | Audit log writes |
dto.go | DTOs |
All endpoints live under /api/apps/bulletin/ on the authed group, with middleware ordered as appEnabled, then policy, then handler.
| Group | Routes | Handlers |
|---|---|---|
| settings | GET /settings, PUT /settings, GET /pending-count | ct.getSettings, ct.updateSettings, ct.pendingCount |
| categories | GET /categories, POST /categories, PUT /categories/:id, DELETE /categories/:id | ct.listCategories, ct.createCategory, ct.updateCategory, ct.deleteCategory |
| posts | GET /posts, POST /posts, GET /posts/:id, PUT /posts/:id, PUT /posts/:id/status, PUT /posts/:id/pin, PUT /posts/:id/comments-setting, GET /posts/:id/comments | ct.listPosts, ct.createPost, ct.getPost, ct.updatePost, ct.setPostStatus, ct.setPostPinned, ct.setPostCommentsSetting, ct.listPostComments |
| comments | GET /comments, PUT /comments/:id/status | ct.listComments, ct.setCommentStatus |
| reports | GET /reports, PUT /reports/:id/status | ct.listReports, ct.setReportStatus |
| blocks | GET /blocks, POST /blocks, DELETE /blocks/:id | ct.listBlocks, ct.createBlock, ct.revokeBlock |
| audit | GET /audit-log | ct.listAuditLog |
Connections to Other Services
- Access control — Every route passes
apps.AppEnabledGuard(d); the app must be enabled for the organization first (see the Add-on Apps Platform). Policy metadata usesPolicyModuleLineOaat the read, readAll, create, update, and delete levels. - Tables (schema
bulletin) —bulletin_board,bulletin_post,bulletin_engagement,bulletin_audit/audit_log, plus the comment, report, and block tables. - Database triggers — Own the counter columns and take advisory locks 4201 and 4202.
- CLS —
organizationIdandlineOaId, carried throughrepo.scoped. - Related modules — Add-on Apps Platform, the Loyalty & Rewards App, and the Appointment Booking App.