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 identity — user_id (the real LINE userId), display_name, picture_url, user_info JSONB and language.
Additional collected data — firstname, lastname, email, mobile_no.
Follow status — follow (enum CommonConfirm, yes/no), registered_date, unfollow_date, and user_type (enum LineUserType: guest or member, where member means the user has registered).
Recent activity — last_activity_type (ActivityType), last_activity_period (ActivityPeriod: morning / afternoon / evening / night), last_activity_date and last_activity_status.
Flexible data — custom_attribute JSONB defaulting to {}, whose valid keys are defined in Attribute Definitions.
Audience membership — audience_ids JSONB with a GIN index, listing the audiences the user belongs to. It is deliberately denormalized so membership queries stay fast.
Rich menu — rich_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.
Related Files
prisma/schema.prisma:492— modelLineUsermanual-sql/1.notify_attribute_change.sql— the first trigger version, covering onlycustom_attributemanual-sql/3.bank.sql— the second version, adding fixed columns and theapp.skip_triggersswitchmanual-sql/5.sql— the latest version, which also writes toevent_outboxseed-data/07.line_user.sql— seeds two sample friendsschema-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 theattribute_changedchannel 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.