Content Listing via Shared Link (Content Link)
Overview
A Content Link is a link an administrator creates in the CMS with filters baked in — category, subcategory, date range, and audience — then places on a rich menu or sends in a chat. When a user opens it, they get an article listing that is already filtered, with no filter parameters sent from the client at all.
The endpoint also increments the link's click_count so the CMS can report on usage.
Business Flow
GET /api/public-content/links/:token/contents (rate limit 10/60s)
Accepted query parameters: search (sanitized, pattern-checked, maximum 100 characters, exactly as in the general listing), page between 1 and 100, and limit between 1 and 50.
- Look up the active
content_linkrow by token — not found returns 404 withContent link not found or inactive. validateLineOa(link.lineOaId)— the OA must be active, otherwise 404 withInvalid or inactive channel.- Resolve the user's audience on a best-effort basis. If an
x-liff-tokenheader is present it is verified, but every failure is swallowed and processing continues without audience filtering — a graceful degrade rather than a rejection. - Call
IncrementClickCount(link.id)to record the click before fetching data. - Defaults are
page=1andlimit=10; askipabove 500 returns 400 withPage number exceeds maximum allowed. - Build the filter from the values embedded in the link, not from the query:
categoryId,subcategoryId(expanded to all descendants), andpublishedDateFrom/publishedDateTo, formatted as2006-01-02T15:04:05.000Z. - A three-tier audience ladder, evaluated in order:
- If the link has a non-empty
audience_ids, filter by the link's audience. - Otherwise, if the user has an audience, filter by the user's audience.
- Otherwise apply
ApplyPublicOnly— show only articles with no audience restriction.
- If the link has a non-empty
- The
searchquery parameter still applies, working alongside the link's own filters. - Hydrate translations and taxonomy references using the same helpers as the general listing.
- Return
{linkInfo, data, total, page, limit, totalPages}, wherelinkInfocarriesname,description, and the category/subcategory references resolved from their ids without additional filtering.
Key Files & Functions
| Item | Value |
|---|---|
| Route | GET /api/public-content/links/:token/contents (rate limit 10/60s) |
| Handler | internal/publiccontent/handler.go → (*Handler).GetContentsByLinkToken |
| Service | internal/publiccontent/service.go → (*Service).GetContentsByLinkToken |
| DTO | byLinkDTO, accepting search, page, limit |
| Repository | internal/contentlink/repository.go → FindActiveByToken, IncrementClickCount |
| Entity | internal/contentlink/entity.go → ContentLink (audience_ids, category_id, subcategory_id, published_date_from, published_date_to, click_count) |
| Filter | internal/contentpage.ListFilter (LinkAudienceIDs, UserAudienceIDs, ApplyPublicOnly, SubcategoryIDs) |
| Response | ByLinkResponse and linkInfo |
Connections to Other Services
- Database — tables
content_link,content_page,content_page_translation,content_category,content_subcategory,line_oa, andline_user(for audience data) - LIFF authentication — calls
VerifyAndGetLineUseron a best-effort basis - Related features — shares repositories and helpers with the public content listing, and serves as the destination for content-link menu items via
contentLinkToken,contentLinkBehavior, andcontentLinkDisplayStylein the public menu - client-web — corresponds to the
content-link-listingfeature