Skip to main content

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.

  1. Look up the active content_link row by token — not found returns 404 with Content link not found or inactive.
  2. validateLineOa(link.lineOaId) — the OA must be active, otherwise 404 with Invalid or inactive channel.
  3. Resolve the user's audience on a best-effort basis. If an x-liff-token header is present it is verified, but every failure is swallowed and processing continues without audience filtering — a graceful degrade rather than a rejection.
  4. Call IncrementClickCount(link.id) to record the click before fetching data.
  5. Defaults are page=1 and limit=10; a skip above 500 returns 400 with Page number exceeds maximum allowed.
  6. Build the filter from the values embedded in the link, not from the query: categoryId, subcategoryId (expanded to all descendants), and publishedDateFrom / publishedDateTo, formatted as 2006-01-02T15:04:05.000Z.
  7. 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.
  8. The search query parameter still applies, working alongside the link's own filters.
  9. Hydrate translations and taxonomy references using the same helpers as the general listing.
  10. Return {linkInfo, data, total, page, limit, totalPages}, where linkInfo carries name, description, and the category/subcategory references resolved from their ids without additional filtering.

Key Files & Functions

ItemValue
RouteGET /api/public-content/links/:token/contents (rate limit 10/60s)
Handlerinternal/publiccontent/handler.go(*Handler).GetContentsByLinkToken
Serviceinternal/publiccontent/service.go(*Service).GetContentsByLinkToken
DTObyLinkDTO, accepting search, page, limit
Repositoryinternal/contentlink/repository.goFindActiveByToken, IncrementClickCount
Entityinternal/contentlink/entity.goContentLink (audience_ids, category_id, subcategory_id, published_date_from, published_date_to, click_count)
Filterinternal/contentpage.ListFilter (LinkAudienceIDs, UserAudienceIDs, ApplyPublicOnly, SubcategoryIDs)
ResponseByLinkResponse and linkInfo

Connections to Other Services

  • Database — tables content_link, content_page, content_page_translation, content_category, content_subcategory, line_oa, and line_user (for audience data)
  • LIFF authentication — calls VerifyAndGetLineUser on 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, and contentLinkDisplayStyle in the public menu
  • client-web — corresponds to the content-link-listing feature