Friend Source Tracking (Friend Track)
Overview
Friend Track answers the question "where did this friend come from?" by creating tracking campaigns whose add-friend invitation links carry a ref code per channel. When someone follows the OA through one of those links, the system records which ref brought them in.
The module is built for large ref volumes, so refs can be uploaded as a CSV — one ref per staff member, per branch, or per printed QR code across thousands of them — and the generated links can be downloaded back out as a CSV.
Business Flow
Creating and managing campaigns
POST /api/friend-trackcreates a tracking campaign with a name, an active period and ref settings.GET /api/friend-trackreturns a paginated campaign list, filterable viaFilterFriendTrackCampaignDto.PUT /api/friend-track/:idupdates a campaign;DELETE /api/friend-track/:idremoves it.
Handling refs at scale
POST /api/friend-track/:id/refs/uploadaccepts a multipart CSV of refs. The system uses batched raw SQL of theINSERT ... ON CONFLICTform to handle high volumes while preventing duplicates.GET /api/friend-track/:id/refs/statusis polled to see whether the upload is still running, finished, or failed.GET /api/friend-track/:id/refs/downloaddownloads every link and ref as a CSV, streamed directly to the response rather than being buffered in memory.
Reporting
GET /api/friend-track/:idreturns campaign details.GET /api/friend-track/:id/reportreports how many friends each ref brought in, filterable by date range or ref viaFilterFriendTrackReportDto. Note that user counts in this report are computed withgetRawMany().lengthrather thanCOUNT(), matching the legacy behaviour.- When someone adds the OA as a friend through a tracked link, webhook-go records the event
in
friend_track_event, linked to both thefriend_track_campaignand the ref used.
Key Files & Functions
The code lives in internal/modules/friendtrack/, split across controller.go,
service.go and dto.go.
| Method | Route | Handler | Policy |
|---|---|---|---|
| GET | /api/friend-track | ct.findAll | readAll friend-track |
| GET | /api/friend-track/:id | ct.findByID | read friend-track |
| GET | /api/friend-track/:id/report | ct.getReport | read friend-track |
| GET | /api/friend-track/:id/refs/download | ct.downloadRefLinks | read friend-track |
| GET | /api/friend-track/:id/refs/status | ct.getRefUploadStatus | read friend-track |
| POST | /api/friend-track | ct.create | create friend-track |
| POST | /api/friend-track/:id/refs/upload | ct.uploadRefs | update friend-track |
| PUT | /api/friend-track/:id | ct.update | update friend-track |
| DELETE | /api/friend-track/:id | ct.delete | delete friend-track |
Every route is wrapped in modulegate.ModuleGate(d, "friend-track").
PolicyModuleFriendTrack is also used as the policy metadata for the GET /api/apps and
PUT /api/apps/:appId endpoints — behaviour inherited from the legacy system.
Connections to Other Services
- Permissions — requires
ModuleGate("friend-track")and carriesPolicyModuleFriendTrackas metadata. - Tables —
friend_track_campaign,friend_track_event,line_userandline_oa. - Storage — ref CSV files are kept in object storage.
- Event writer — line-management-webhook-go records the events when follow events arrive.
- Related modules — LINE User Management, Tracking Link and All Friend Listing.