Skip to main content

Content Link Collections

Overview

A Content Link is a token-bearing URL that does not point at a single article. Instead it stores a content selection rule, and when a recipient opens the link the system resolves that rule at request time and renders the matching articles as a list.

The practical benefit is that a marketing team creates the link once, drops it into a rich menu, a campaign, or a LINE message, and the content behind it keeps updating as new matching articles are published — no link maintenance required.

Each link can be scoped by category, subcategory, publish date range, audience segments that gate who may view it, and an active/inactive status. Every link tracks a click count and can be reissued with a fresh token the moment it leaks.

The entire feature lives on a single page; create and edit both open in a modal rather than navigating away.

Business Flow

  1. Opening the Content Links page loads the link list together with the category options used by the filter, and fetches the current LINE OA so its LIFF ID is available for link construction.
  2. The table shows the link name, a badge summary of the configured rule (category, subcategory, date range, audience count, or a note that no filter is set), the click count, the creation date, and the row actions.
  3. The toolbar offers three filters: search by name, status, and category.
  4. Creating a link opens the form modal, which loads category and audience options in parallel. Choosing a category clears the previously selected subcategory and reloads the subcategory options for that category — the subcategory field stays disabled until a category is picked.
  5. Form fields are name (required), description, a "filter criteria" block (category, subcategory, date range, and multi-select audiences), and an active/inactive switch.
  6. On submit the form builds the payload explicitly: the switch becomes an active or inactive status, the date range is formatted as YYYY-MM-DD, and cleared optional values are sent as null so that an edit can genuinely remove an existing condition.
  7. Copying the link yields one of two shapes. If the link restricts audiences and the OA has a LIFF ID, the CMS produces a LIFF URL so the link opens inside the LINE app and the viewer can be identified. Otherwise it produces a plain web URL built from the OA hash and the token.
  8. Regenerating the token prompts a confirmation warning that the previous link stops working immediately. Deleting a link is likewise confirmed.

Key Screens & Components

List page (src/app/content-links/page.tsx) — combines the table, the filters, both link-copy variants, token regeneration via POST /content-links/{id}/regenerate-token, and deletion.

Form modal (src/app/content-links/components/ContentLinkFormModal.tsx) — shared by create and edit. It owns option loading, keeps the subcategory list in sync with the chosen category, and assembles the payload before submitting.

Shared service (src/services/content-link.service.ts) — covers list, read, create, update, delete, token regeneration, and a preview-contents call. The preview call is not used by this page; it is consumed by the Menu Builder to render inline content-link previews.

Core data shape — a link record carries its token, click count, the owning OA (whose hash builds the public URL), and the referenced category and subcategory. The preview response returns the matching content items with their public token, slug, title, excerpt, cover image, and publish date, alongside pagination metadata.

Dependencies

  • Permissions — the subject content-links keys the submenu entry under Content Management and is unlocked by the backend line-oa module.
  • Content Management — the source of the articles a link selects.
  • Content Category and Subcategory — the primary selection criteria; subcategory options come from the hierarchical dropdown endpoint.
  • Audience — assigning audiences flips the link into LIFF form so the platform can identify who opened it.
  • Menu Builder — menu items of type content_link reference a link's token and call the preview endpoint to render content inside the menu preview.
  • Environment configuration — public URLs are built from NEXT_PUBLIC_BASE_APP_URL, and API traffic goes through NEXT_PUBLIC_BASE_CONSOLE_API_URL.

Backend Details (CMS API)

The module lives in internal/modules/contentlink/, registered under the authenticated /api/content-links group. The link that recipients actually open is served by a separate public module.

Permissions and data scoping

  • Every endpoint behind this screen requires the global JwtAuth.
  • Handlers declare line-oa policy metadata (readAll / read / create / update / delete), but it is not enforced yet and no ModuleGate guards the module — the effective control is frontend menu hiding.
  • The service reads userId, lineOaId, and organizationId from the request context to build its user context, so both listing and creation are automatically bound to the OA currently in scope. None of these values are accepted from the payload.
  • On POST /api/content-links the backend generates the token itself from a cryptographic random source (crypto/rand) rendered as hex. The frontend does not choose the token and should not attempt to supply one.
  • POST /api/content-links/:id/regenerate-token overwrites the existing token, so the old link stops working the instant the call succeeds — no grace period and no way back to the previous token. That is why the UI gates it behind a confirmation.
  • Since the token is the only thing protecting the public link, anyone who obtains it can view the content list. Audience restriction is therefore a layer that depends on LIFF identifying the viewer, not on the token staying secret.

GET /api/content-links/:id/preview-contents takes page and limit and returns the content the link will actually render, evaluating the rule at call time. It is useful for confirming a rule behaves as intended before sending — for instance a date range narrow enough to match nothing at all. The Menu Builder also calls this endpoint to render inline content previews inside menus.

  • Recipient traffic does not hit /api/content-links; it goes to the public GET /api/public/contents endpoint, which requires no login.
  • That public endpoint enforces a rate limit of 10 requests per 60 seconds, a ceiling worth keeping in mind when one link is sent to a large audience at once, or when the landing page refetches repeatedly. See the public pages documentation for details.
  • The click counter shown in the table is incremented from the public link path, not by pressing preview in the back office.

Other notes

  • DELETE /api/content-links/:id responds with status 204 and an empty body, per the system's existing contract — callers should not try to parse JSON from that response.
  • PATCH /api/content-links/:id accepts null values so an edit can genuinely clear an existing condition, matching the frontend's habit of sending cleared fields explicitly as null.
  • Tables involved: content_link is primary, read alongside content_page, content_category, content_subcategory, and line_oa.