Skip to main content

LINE Friends (LINE User)

Overview

line_user holds the end users who have added the LINE OA as a friend — a distinct population from CMS users. It is the largest and most frequently read and written table in the system, fed from three directions: webhooks (follow events), follower syncs from LINE, and CSV imports.

Beyond the basic LINE profile, the table also stores custom attributes (flexible customer data as JSONB), follow status, recent activity, and the list of audiences the user currently belongs to.

Core Data Structure

line_user (model LineUser)

The primary key is a UUID generated by gen_random_uuid().

LINE identityuser_id (the real LINE userId), display_name, picture_url, user_info JSONB and language.

Additional collected datafirstname, lastname, email, mobile_no.

Follow statusfollow (enum CommonConfirm, yes/no), registered_date, unfollow_date, and user_type (enum LineUserType: guest or member, where member means the user has registered).

Recent activitylast_activity_type (ActivityType), last_activity_period (ActivityPeriod: morning / afternoon / evening / night), last_activity_date and last_activity_status.

Flexible datacustom_attribute JSONB defaulting to {}, whose valid keys are defined in Attribute Definitions.

Audience membershipaudience_ids JSONB with a GIN index, listing the audiences the user belongs to. It is deliberately denormalized so membership queries stay fast.

Rich menurich_menu_id FK to rich_menu.id and rich_menu_archive_id FK to rich_menu_archive.id, identifying the rich menu currently bound to this user.

The real business key is the unique constraint on (user_id, line_oa_id, organization_id), which tracking_log references as a composite FK. Other indexes are (line_oa_id) and the GIN index on audience_ids.

The trg_line_user_attribute_change trigger

An AFTER UPDATE trigger that calls notify_attribute_change(), which issues a pg_notify on the attribute_changed channel carrying the list of keys that changed. It covers both fixed columns (display_name, email, mobile_no, follow) and keys inside custom_attribute, referenced in the form custom.<key>.

A current_setting('app.skip_triggers')='true' escape hatch temporarily disables it during bulk updates so the trigger does not fire in a tight loop. See Event Outbox for the full picture.

  • prisma/schema.prisma:492 — model LineUser
  • manual-sql/1.notify_attribute_change.sql — the first trigger version, covering only custom_attribute
  • manual-sql/3.bank.sql — the second version, adding fixed columns and the app.skip_triggers switch
  • manual-sql/5.sql — the latest version, which also writes to event_outbox
  • seed-data/07.line_user.sql — seeds two sample friends
  • schema-dumps/2026-07-24/schema.sql:2243

Connections to Other Services

  • webhook-go — creates and updates rows on follow, unfollow and message events.
  • worker-go — syncs followers from LINE, processes import jobs, refreshes audience_ids, and listens on the attribute_changed channel to fire trigger rules and workflows.
  • cms-api-go — powers the "all friends" screen, including search and filtering by custom attribute, and export.
  • client-api-go — lets LIFF read and update the user's own profile, and links loyalty, bulletin and appointment records by LINE userId.
  • Connects to Audience, Attribute Definitions, Rich Menu and Tracking.