GET /users/referrals.json

狀態

已補 action / routing / PHPUnit;staging new 實測待補。

這支只負責回傳目前登入 user 的推薦好友列表。legacy 主流程讀 user_referrals 與被推薦 user 資料,不寫 user_referrals、不寄信、不寫 queue、不做付款。寄推薦信是另一支 POST /users/refer.json,後續應另外建文件,不和這支混在一起。

Legacy 流量

2026-07-08 查 legacy 正式機 ip-10-5-2-242/var/log/apache2/get-lancer_access.log*

Method / pathstatuscount
GET /users/referrals.json200139
OPTIONS /users/referrals.json200125

同一批查詢也看到 POST /users/refer.json 有 2 筆 200,但語意不同,先不列入本次搬移。

操作路徑

Web 專家推薦好友頁會觸發這支 API。實際 URL:

https://staging.pro360.com.tw/dashboard/free-credits/referral

前端在 ProReferral mount 時呼叫 APIManager.getReferralInfo(),用 session token GET /users/referrals.json,再把 response.referred_users 放進畫面 state。

檔案行為
/Users/mattsu/Documents/Site/web-app/modules/routes.js:734/dashboard/free-credits(/:index) route。
/Users/mattsu/Documents/Site/web-app/modules/components/dashboard/FreeCredits.js:17-20EVENT_SLUGS.REFERRAL = referral
/Users/mattsu/Documents/Site/web-app/modules/components/dashboard/FreeCredits.js:247-250index === referral 時 render ProReferral
/Users/mattsu/Documents/Site/web-app/modules/utils/api-manager.js:3441-3449getReferralInfo() 讀 session token,GET /users/referrals.json
/Users/mattsu/Documents/Site/web-app/modules/components/dashboard/ProReferral.js:24-28component mount 後呼叫 getReferralInfo(),使用 data.referred_users

referred_users 的前端用途:

  • referred_users.length:顯示「累積推薦人數」與進度條。
  • UserProfile.first_name:列表顯示推薦好友名字。
  • User.quote_status_id:顯示「已註冊 / 已報價 / 已接案」進度。
  • User.awarded_credits:顯示 $ +{credit}

Legacy 對照

項目legacy
controller/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/UsersController.php:5697-5748
session helper/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/RestApiHelper.php:75-152
session user id + full request log/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/RestApiHelper.php:162-182
referral model/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/UserReferral.php:1-83

Legacy 流程:

  1. 預設 response 是 error=1 + message=Invalid request
  2. 必須是 JSON request 且通過 RestApiHelper::hasValidApiSession($this)
  3. blocked user 會直接回 HTTP 403 + {"error":5,"message":"Blocked User"}
  4. 從 session 取得目前 user_id
  5. user_referrals
    • user_id = current user_id
    • created > CURRENT_DATE - INTERVAL 1 YEAR
    • fields 只有 idreferee_user_idquote_status_idawarded_credits
  6. 用 referral rows 的 referee_user_idusers
    • fields 只有 User.id
    • contain UserProfile.first_name
    • contain UserProfile.last_name
    • contain UserAvatar.amazon_s3_original_url
    • contain UserAvatar.amazon_s3_thumb_url
  7. 把 referral row 的 quote_status_idawarded_credits 塞進對應 user 的 User 區塊。
  8. referred_userserror=0message=Success

Invalid session 或拿不到 user_id 時,legacy 回 error=2message=Invalid session

New 對照

項目new
endpointEndpoint/V1/Users.php:1082-1113 已補 referrals()
DB formatterEndpoint/V1/Users.php:1145-1290 已補 getLegacyReferralUsers() 與 legacy response formatter。
routingLib/Common/RouterRule/Mapping.php:108-111 已補 /users/referrals.json explicit route。
existing referral modelLib/Model/UserReferral.php:15-74 目前有 relation / add helper,但沒有這支 API 需要的 list response method。
user avatar referenceEndpoint/V1/Users.php:88-97 既有 user response 有用 Attachment::getClassRow('UserAvatar', user_id),本 API 實作時仍要以 legacy contain 欄位為準。
PHPUnittests/UsersReferralsTest.php:71-203 覆蓋空列表、nested shape、1 年 filter、inactive referred user、missing avatar、blocked user、invalid session。

Request

必要 headers:

X-PRO360-Rest-Api-Key: <api key>
X-PRO360-User-Session-Token: <session id>

沒有 body。

最小 curl:

curl -sS 'https://api-staging.pro360.com.tw/users/referrals.json' \
  -H 'X-PRO360-Rest-Api-Key: <api key>' \
  -H 'X-PRO360-User-Session-Token: <session id>'

Response

Valid session success:

{
  "referred_users": [
    {
      "User": {
        "id": "123",
        "quote_status_id": "0",
        "awarded_credits": "0"
      },
      "UserProfile": {
        "first_name": "First",
        "last_name": "Last"
      },
      "UserAvatar": {
        "amazon_s3_original_url": "...",
        "amazon_s3_thumb_url": "..."
      }
    }
  ],
  "error": 0,
  "message": "Success"
}

Valid session 但沒有 referral:

{
  "referred_users": [],
  "error": 0,
  "message": "Success"
}

2026-07-08 legacy staging 實測已確認 valid session + 空資料回:

{"referred_users":[],"error":0,"message":"Success"}

Invalid / missing session:

{
  "error": 2,
  "message": "Invalid session"
}

Blocked user:

{
  "error": 5,
  "message": "Blocked User"
}

HTTP status:403

有 referral 資料時的 response 型別待 staging legacy sample 確認。legacy Cake 通常會把 DB 欄位值以字串回傳;目前 PHPUnit 先固定 User.idquote_status_idawarded_credits 為字串。

DB Read

先用容器 PHP 8.2 查過相關 schema:

Table欄位
user_referralsidcreatedmodifieduser_idreferee_user_idquote_status_idawarded_creditsconfirmed_phone
users本 API legacy 只取 id,不要因 schema 有其他欄位就補進 response。
user_profiles本 API legacy 只取 first_namelast_name
attachments本 API legacy 只取 class=UserAvataramazon_s3_original_urlamazon_s3_thumb_url

Legacy 實際讀取:

SELECT id, referee_user_id, quote_status_id, awarded_credits
FROM user_referrals
WHERE user_id = :current_user_id
  AND created > CURRENT_DATE - INTERVAL 1 YEAR;

第二段查被推薦 user 時,要維持 legacy 語意:

  • 用第一段的 referee_user_idusers.id
  • 只回 User.id
  • UserProfile.first_nameUserProfile.last_name
  • UserAvatar.amazon_s3_original_urlUserAvatar.amazon_s3_thumb_url
  • 再把第一段的 quote_status_idawarded_credits 放回 User

Side Effect

這支沒有以下業務 side effect:

  • 不新增 / 修改 / 刪除 user_referrals
  • 不寄 email
  • 不寫 event_queue / task_event_queue
  • 不寫 payment / wallet / transaction
  • 不產生 activity

Blocked user guard 是 session helper 行為,不是 referrals 主流程;blocked 時不查 referral list,不更新 access time。

Session helper side effect:legacy RestApiHelper::hasValidApiSession() 在特定條件會更新 session user 的 app last access;new 沿用 findLegacyApiSessionUser() + applyLegacyApiSessionSideEffects()

Parity 注意

不可自行新增 legacy 沒有的條件或欄位:

  • 不加 users.is_active = 1
  • 不加 attachments.review_status
  • 不加 order
  • 不加 limit
  • 不回 referral_hash
  • 不回 confirmed_phone
  • 不回 users.email / users.phone
  • 不把 /users/refer.json 的寄信邏輯放進來

Request log:legacy RestApiHelper::getUserId() 會記 full request info;new 搬移時先依目前 Users 已搬 API 慣例保留 RouterV3 generic request log,是否補 endpoint duplicate log 待確認。這支 request 沒有 body,敏感欄位風險低,但仍不得為了 log 自行新增 response / DB side effect。

Tests Plan

已補:

  • valid session + 有 referral:回 legacy nested shape,含 User / UserProfile / UserAvatar
  • valid session + 無 referral:回 referred_users: []error=0message=Success
  • missing / invalid session:回 error=2message=Invalid session
  • blocked user:回 HTTP 403 + {"error":5,"message":"Blocked User"}
  • referral 超過一年:不回。
  • 被推薦 user inactive:若 legacy 實測仍回,new 不得加 is_active = 1 擋掉。
  • response 不包含 referral_hashconfirmed_phoneemailphone
  • 不寫 queue / activity / user_referrals。

測試指令:

docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/UsersReferralsTest.php

結果:OK,7 tests / 40 assertions。

待確認

  • staging legacy 有 referral 資料時的完整 JSON shape 與欄位型別。
  • staging legacy 有 referral 但沒有 UserAvatar 時的完整 association shape;目前依 Cake hasOne empty association 與 PHPUnit 固定為 []
  • new staging curl / DB log 驗證。
  • request log 是否需要補 endpoint-level duplicate log;目前保留 RouterV3 generic log,維持目前 Users 搬移慣例。