Public API for End Users
Overview
The publicmod module gathers the endpoints that ordinary LINE users can call without logging into the CMS. Its main consumer is client-web, which uses them to render content pages and menus shared through LINE.
Because these endpoints are publicly reachable, the ones that return content listings are wrapped in a rate limit of 10 requests per 60 seconds. Token-addressed endpoints are not throttled, since the token itself is unguessable.
The module also supports LIFF: it returns the configuration client-web needs to initialize LIFF, and it accepts an x-liff-token header to identify the LINE user currently viewing the page.
Business Flow
Browsing Content Listings
GET /api/public/contents, with query parameters defined byPublicContentQueryDto, returns published content as a payload containingdata,total,page,limit, andtotalPages. Throttled at 10 requests per 60 seconds.GET /api/public/contents/categories, with alineOaIdquery parameter, returns the categories that currently hold content. Throttled the same way.
Opening by Token
GET /api/public/menu/:tokenopens a menu created in Menu Builder.GET /api/public/content/:token/liff-configreturns that page's LIFF configuration so client-web can callliff.init()before loading content.GET /api/public/content/:token, with an optionalpasswordquery parameter, opens a content page.- If the
x-liff-tokenheader is present, it identifies the LINE user so the visit can be recorded against a known person. - Password-protected pages require the correct
password. - A missing or unpublished page returns error
APP_007.
- If the
Soft-Delete Parity Worth Knowing
- The
content_page,content_category, andcontent_subcategorytables use a plaindeleted_datecolumn, so the service must add the filtering predicate itself — TypeORM does not do it automatically. - The
line_oatable has a real@DeleteDateColumn, so GORM filters it automatically. ThedeletedAt: IsNull()condition that appears in the original TypeScript where clause points at a property that does not exist and is therefore a no-op.
Key Files & Functions
The code lives in internal/modules/publicmod/, made up of controller.go, service.go, liff.go, and dto.go.
| Method | Route | Handler | Guard |
|---|---|---|---|
| GET | /api/public/contents | ct.listPublicContents | public + Throttle(10, 60000) |
| GET | /api/public/contents/categories | ct.listPublicCategories | public + Throttle(10, 60000) |
| GET | /api/public/menu/:token | ct.getMenuByToken | public |
| GET | /api/public/content/:token/liff-config | ct.getContentLiffConfig | public |
| GET | /api/public/content/:token | ct.getContentByToken | public |
Static routes are registered before the :token routes so gin resolves to the correct handler. RegisterRoutes uses only the public group; the _ = authed line confirms the authed group is intentionally unused.
Several parallel public endpoints live in other modules:
| Route | Module |
|---|---|
GET /api/public/content-pages/:token | Content Page |
POST /api/public/content-pages/:token/track | Content Page |
GET /api/menu-builder/public/:token | Menu Builder |
Connections to Other Services
- Access control — None; everything here is public. Protection comes from unguessable tokens plus throttling.
- Tables —
content_page,content_page_translation,content_category,content_subcategory,content_link,menu_builder, andline_oa - Middleware —
middleware.Throttle(10, 60000)(see the Core HTTP Platform) - LIFF —
liff.govalidatesx-liff-tokenand returns the configuration to client-web. - Consumer — client-web-2026
- Related modules — Content Page, Content Link, Menu Builder, and Content Category.