POST /users/sign_up.json

快速結論

  • 狀態:已補 Users::sign_up() 與 PHPUnit;已做 staging qna path 實測,完整 social token diff 待補。
  • 2026-06-24 查 legacy 正式機 access log,POST /users/sign_up.json 有 894 筆 200,另有 201 筆 OPTIONS 200
  • 這支是 consumer 註冊 API,會建立 usersuser_profilesuser_loginsapi_sessions、activity,並可能寫 referral、phone verification side effect、Consumer_Signup event。
  • 搬移時只以 legacy 實際執行路徑為準;不可直接沿用 search_add / quote_services/add auto signup helper。
  • 2026-06-26 已確認 web-app Google 登入不會自然 fallback 呼叫本 API;web Google 目前走 /users/login.json。本 API 的 Google branch 是 backend legacy capability,只有 caller 明確送 User[id_token]/users/sign_up.json 才會執行。
  • migration decision:本次經 reviewer 確認,legacy 有 sign_up endpoint 專用 request log,new 不再補這份 duplicate full request log;保留 RouterV3 generic request log,password/passwd key 由 router 層過濾。

Legacy 對照

檔案行號行為
/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/UsersController.php6044-6049JSON request + valid API key guard
同上6071-6104ibon / qna 判斷、required / optional input、trim
同上6106-6117email blacklist、password、第一次 country_code + phone、platform、device id
同上6118-6149Google / Facebook / Facebook limited / Apple social auth,並用 social id 找既有 user
同上6151-6163social id email mismatch 時忽略該 existing user;email 已存在回 email_existed
同上6166-6176verify_code 時第二次 prepend country_code,再跑 PhoneVerification::afterAuth()
同上6179-6243users insert data,User::save(false),失敗回 create_user_failed
同上6245-6258verified phone side effect、建立 user_profiles
同上6260-6274user_logins、referral、建立或更新 api_sessions
同上6276-6285RequestorSignup activity;非 ibon/qna 才送 welcome event 與 CRM upsert
同上6287-6296success response
/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/RestApiHelper.php297-313getSafeUsername()
/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/User.php573-629sendWelcomeMail()Consumer_Signup event,task 是 Moengage
/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/User.php1118-1151setVerifiedPhoneForUser() normalize phone、清其他 user phone confirm、更新目前 user
/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/UserReferral.php59-69addReferral() 依年度上限寫 user_referralsuser_over_referrals

New 對照

檔案行號行為
Endpoint/V1/Users.php585-721sign_up() action:guard、input、DB write、side effect、response;不再寫 endpoint duplicate request log
同上1353-1364legacy-shaped generic error response
同上1366-1385支援 nested User 與 flat User[...] form key
同上1387-1473social auth 與 existing user lookup;lookup 不加 is_active filter
同上1475-1531users insert allowlist
同上1533-1548user_profiles insert allowlist
同上1550-1577scoped insert helper,只允許本 API 會寫的 table
同上1579-1622legacy safe username
同上1624-1757verified phone side effect、Reset_Phone_Confirm event
同上1759-1833user login device id、referral、ApiSession::upsert() 前補 legacy $_SERVER API key
同上1835-1922Consumer_Signup event payload、7 天內 SecurityHash reuse,沒有才新增
Lib/Common/RouterV3.php210, 266-289generic request log;password/passwd key 過濾由 router 層統一處理
tests/UsersSignUpTest.php97-192success path:User[...] multipart key、user/profile/login/session/activity/event/phone verification
同上194-242phone conflict:舊 user phone reset 並寫 Reset_Phone_Confirm event
同上244-354invalid API key、missing verify code、inactive email、country code bug、ibon path
同上356-461social 欄位 mapping 與 negative assertion:不可補高風險 insert 欄位,不可加 is_active lookup guard,不可替 Apple code lookup 加 legacy 沒有的 order

Input / Response

項目行為
methodPOST
auth全域 API key;不需要 user session
body shapenested User 或 multipart User[...]
required一般 user:emailnamephonedevice_idverify_code
ibon / qnais_ibon_user=1is_qna_user=1 時不要求 phone / verify_code,且不送 welcome event
success response{"error":0,"status":"success","user_id":...,"username":email,"session_id":...}
known error response{"error":<code>,"status":"<legacy status>"}
generic exception{"error":999,"status":<translated err_new_api_999>}

Frontend Caller

2026-06-26 檢查 /Users/mattsu/Documents/Site/web-app

流程web-app caller實際 API備註
Facebook signup fallbackmodules/utils/api-manager.js::signupWithFB()/users/sign_up.json會送 User[is_qna_user]=1User[facebook_user_id]User[access_token]
Google loginmodules/utils/api-manager.js::googleLogin()/users/login.jsonweb 不會自然 fallback 到 /users/sign_up.json
Apple loginmodules/utils/api-manager.js::appleLogin()/users/login.jsonweb 不會自然 fallback 到 /users/sign_up.json

因此驗證本 API 的 Google branch 不能只從 web Google 登入觸發;需要 app / 其他 caller 明確呼叫 /users/sign_up.json,或用 curl 帶真實 User[id_token]User[platform]

已知 error:

codestatus
1invalid request
2field <display> required
3invalid_email
4email_existed
5phone_verification_failed
6create_user_failed

Insert 欄位分類

users

欄位來源備註
created / modifiednew explicit timestamplegacy Cake save 自動時間
role_idlegacy literal固定 2
usernamelegacy safe usernameemail local part,必要時加 suffix
emailUser[email]trim 後使用
passwordUser[password]只有非空才用 Pro360LoginUtil::getCryptHash() 寫入
device_idUser[device_id]required;ibon/qna 仍 required
midUser[mid]未帶時 explicit NULL,對齊 legacy $data['mid'] assignment
is_activelegacy literal固定 1
ip_idIp::toSaveIp()request IP
mobile_app_hashlegacy hashmd5(_unum() . username . password . Security.salt)
registration_sourceApiKey::getClientByKey(header api key)依 API key client
multi_lang_idMultiTranslation::getHeaderLangId()accept-language
social 欄位social token 驗證成功Google / Facebook / Apple 欄位只在驗證成功時寫
entranceUser[entrance]選填

未列在上表的 users 欄位不得放入 explicit insert allowlist。2026-06-26 local DB 確認 @@SESSION.sql_mode=NO_ENGINE_SUBSTITUTION,未 explicit 寫入的 NOT NULL 欄位由 MySQL implicit default 保持 legacy/Cake 行為。

高風險欄位不得 explicit insert:

類型欄位例
wallet / paymentavailable_wallet_amountavailable_balance_amountblocked_amount
countersquote_service_countquote_request_countuser_login_count
app timestampsmobile_app_time_modifiediphone_last_request 初始 insert
securitysecurity_question_idsecurity_answer
notification / subscriptionauto_refill_package_idweekly_budget_id

user_profiles

欄位來源備註
created / modifiednew explicit timestamplegacy Cake save 自動時間
user_id新增的 users.id必寫
first_nameUser[name],空值 fallback username經 scoped StringUtil::filterEmptyString() 等效處理
phone$data['phone']legacy 先設定 phone;ibon/qna 無 phone 時會寫 empty string

Side Effects

類型對齊內容
email blacklistEmailBlacklist::isBadDomain(email),命中回 error 3
existing user guardemail / social lookup 都不加 is_active filter;inactive email 也回 email_existed
country code保留 legacy 雙 prepend 行為;帶 country_code 時可能造成 phone verification failed
phone verificationPhoneVerification::afterAuth(phone, code) 成功後刪除驗證 row
verified phonenormalize phone、清其他 user confirmed phone、寫 reset event、更新目前 user phone / confirmed / iphone_last_request;此段用 transaction 對齊 legacy setVerifiedPhoneForUser()
phone conflict email舊 user 有 quote_service_categories.is_auto_quote_on = 1 時寫 Reset_Phone_Confirm event,template 601 Auto Quote Verified Phone Unconfirmed Notification
user loginUserLogin::insertUserLogin($userId, 1, device_id)undefined 字串只在本 API call site 轉 NULL
referral不共用 new UserReferral::addReferral();本 API 依 legacy 年度上限寫 referral / over-referral
sessionApiSession::upsert(),補 legacy HTTP_X_PRO360_REST_API_KEYapi_sessions.api_key 對齊
activityQuoteActivity::createActivity('User', user_id, RequestorSignup, null, user_id, user_id)
welcome event非 ibon/qna 才寫 Consumer_Signup;payload 只含 user_idquote_service_idtasks
CRM非 ibon/qna 呼叫 QuoteService::crmUpsertUser();legacy 實際 Zoho call 已註解
request log本次經 reviewer 確認,new 只保留 RouterV3 generic request log;不補 legacy sign_up endpoint duplicate full request log

Config 搬移清單

Lib/Common/Configuration.php 目前被 .gitignore 忽略;以下 key 要搬到 staging / production 實際部署使用的 ProConfig::$config 或等效部署設定,不走一般 commit。

必搬

keylegacy 來源new 落點用途
site.url對應 legacy 環境的 app/Config/settings.yml;本機 legacy 範例在 /Users/mattsu/Documents/Site/get-lancer-php56/app/Config/settings.yml:271new 部署環境 ProConfig::$config['site.url']sign_up welcome / reset link;staging 搬 staging 值,production 搬 production 值
email.signature對應 legacy 環境的 app/Config/settings.yml;本機 legacy 範例在 /Users/mattsu/Documents/Site/get-lancer-php56/app/Config/settings.yml:22new 部署環境 ProConfig::$config['email.signature']sign_up welcome / phone conflict email replacement
quote.referral_annual_limit/Users/mattsu/Documents/Site/get-lancer-php56/app/Config/settings.yml:212,值 10new 部署環境 ProConfig::$config['quote.referral_annual_limit']referral 年度上限;程式 fallback 10 只是保護,部署設定仍應 explicit 搬

Google token 驗證才搬

來源是 legacy 實際部署環境的 app/Config/settings.yml 或同等 private config,落點是 new 部署環境同名 ProConfig key。本機 legacy checkout 沒有實際值,不能用本機 repo 補。

這些 key 不代表 web-app 會觸發 Google /users/sign_up.json。它們只影響後端收到 id_token 時的 Google token 驗證,例如 /users/login.json,或 caller 明確送 User[id_token] 到本 API 的情境。

key對應 platform
google.oauth_client_iddefault
google.oauth_client_id_iosios
google.oauth_client_id_androidandroid
google.oauth_client_id_ios_consumerios_consumer
google.oauth_client_id_android_consumerandroid_consumer
google.oauth_client_id_android_firebaseandroid_firebase
google.oauth_client_id_android_consumer_firebaseandroid_consumer_firebase

不搬

key原因
Security.salt已在本 API scoped 成 Users::LEGACY_SECURITY_SALT,避免改 global config
areaTW 環境不要設定 area=tw;空值才是目前 phone normalization path

DB seed

DB seed 不是 config 檔搬移。只有目標 DB 缺資料時,才從 legacy DB 搬 row:

  • event_actionsConsumer_Signup email / moengage、Reset_Phone_Confirm email。
  • email_templates601 Auto Quote Verified Phone Unconfirmed Notification,需 is_active=1

2026-06-26 local DB 已確認上述 event_actions / email_templates 存在。2026-06-26 local ProConfig 查詢:site.urlemail.signature 有值;quote.referral_annual_limitNULL;所有 google.oauth_client_id*NULL

實測與測試

執行環境:Docker PHP 8.2,/project-data

docker exec -w /project-data cd63f9147e8d php -l Endpoint/V1/Users.php
docker exec -w /project-data cd63f9147e8d php -l tests/UsersSignUpTest.php
docker exec -w /project-data cd63f9147e8d ./vendor/bin/phpunit tests/UsersSignUpTest.php

結果:

Endpoint/V1/Users.php: No syntax errors detected
tests/UsersSignUpTest.php: No syntax errors detected
tests/UsersSignUpTest.php: OK (11 tests, 128 assertions)

Staging 實測:

  • 2026-06-26 POST /users/sign_up.json,payload 為 User[email]User[name]User[device_id]User[is_qna_user]=1
  • response:{"error":0,"status":"success","user_id":"15782","username":"signup-curl-test-20260626124527@example.com","session_id":"929e4625rdpb9in7eal6n4sdcl"}
  • DB side effect:建立 usersuser_profilesuser_loginsapi_sessionsRequestorSignup activity;因 is_qna_user=1 不寫 Consumer_Signup event,符合 legacy path。

待確認

  • 尚未做 staging vs legacy full diff;上線前需用真實 staging request 比對 response 與 DB side effect。
  • social token path 已補欄位 mapping test;仍未用真實 Google / Facebook / Apple token 實測。