Skip to main content

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.

  1. Every query goes through repo.scoped or scopedTx, which enforce both organization_id and line_oa_id.
  2. 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.
  3. Every mutation shares a transaction with its audit log row.
  4. Counter columns must never be writtencomment_count, reaction_count, and report_count are owned by database triggers.
  5. 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.
  6. Deletion is always status = 'deleted' plus a deleted_date stamp. There is no hard delete.

Settings and Categories

  1. GET /api/apps/bulletin/settings and PUT /api/apps/bulletin/settings configure the board — whether comments are open, whether posts need approval, and so on.
  2. GET /api/apps/bulletin/pending-count returns the number of items awaiting approval, used for the menu badge.
  3. Categories are managed through GET /api/apps/bulletin/categories, POST /api/apps/bulletin/categories, PUT /api/apps/bulletin/categories/:id, and DELETE /api/apps/bulletin/categories/:id.

Posts and Comments

  1. GET /api/apps/bulletin/posts and POST /api/apps/bulletin/posts list and create posts; GET /api/apps/bulletin/posts/:id and PUT /api/apps/bulletin/posts/:id read and edit one.
  2. PUT /api/apps/bulletin/posts/:id/status changes a post's status — published, hidden, or deleted.
  3. PUT /api/apps/bulletin/posts/:id/pin pins a post.
  4. PUT /api/apps/bulletin/posts/:id/comments-setting opens or closes comments on that specific post.
  5. GET /api/apps/bulletin/posts/:id/comments lists a post's comments.
  6. GET /api/apps/bulletin/comments shows the board-wide comment queue, and PUT /api/apps/bulletin/comments/:id/status approves, hides, or deletes a comment.

Safety and Moderation

  1. GET /api/apps/bulletin/reports shows the content report queue, and PUT /api/apps/bulletin/reports/:id/status resolves or dismisses a report.
    • Report rows are never deleted, because the idx_bul_report_once index is what prevents the same user from reporting the same item twice.
  2. GET /api/apps/bulletin/blocks and POST /api/apps/bulletin/blocks list and create user blocks; DELETE /api/apps/bulletin/blocks/:id revokes a block rather than deleting the row.
  3. GET /api/apps/bulletin/audit-log provides read-only access to the staff action history.

Key Files & Functions

The code lives in internal/modules/bulletin/.

FileRole
controller.goRoute registration; every route carries apps.AppEnabledGuard(d)
scope.gorepo.scoped and scopedTx, which enforce tenant scoping
service_board.goSettings, categories, pending-count
service_moderation.goPosts, comments, status transitions, pinning
service_safety.goReports, blocks, audit log reads
audit.goAudit log writes
dto.goDTOs

All endpoints live under /api/apps/bulletin/ on the authed group, with middleware ordered as appEnabled, then policy, then handler.

GroupRoutesHandlers
settingsGET /settings, PUT /settings, GET /pending-countct.getSettings, ct.updateSettings, ct.pendingCount
categoriesGET /categories, POST /categories, PUT /categories/:id, DELETE /categories/:idct.listCategories, ct.createCategory, ct.updateCategory, ct.deleteCategory
postsGET /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/commentsct.listPosts, ct.createPost, ct.getPost, ct.updatePost, ct.setPostStatus, ct.setPostPinned, ct.setPostCommentsSetting, ct.listPostComments
commentsGET /comments, PUT /comments/:id/statusct.listComments, ct.setCommentStatus
reportsGET /reports, PUT /reports/:id/statusct.listReports, ct.setReportStatus
blocksGET /blocks, POST /blocks, DELETE /blocks/:idct.listBlocks, ct.createBlock, ct.revokeBlock
auditGET /audit-logct.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 uses PolicyModuleLineOa at 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.
  • CLSorganizationId and lineOaId, carried through repo.scoped.
  • Related modulesAdd-on Apps Platform, the Loyalty & Rewards App, and the Appointment Booking App.