QuoteServices Add

API:

POST /quote_services/add.json

功能說明

provider 新增服務時呼叫。這支不是單純新增 quote_services;legacy 同時支援既有 session、新 email 自動建立 provider user、social login / signup、service category mapping、外部評價短網址、activity、queue、notification 與 other category 回報。

成功 response:

{"error":0,"service_id":"123","session_id":"...","user_id":"456"}

service_iduser_id 的 JSON 型別都是 string;這是 legacy response contract,不可因 DB id 在新版內部以 int 處理而輸出成 number。

正式機流量

2026-06-12 查正式機 access log:

2209 POST    /quote_services/add.json 200
 887 OPTIONS /quote_services/add.json 200

同次查詢 /quote_services/add/quote_services/add/... legacy HTML path,未看到流量。正式流量只確認到 JSON API。

快速結論

  • 呼叫者:需有效 REST API key;有 user session 時使用 session user,無 session 時依 User[...] 做登入或建立 provider user。
  • legacy API:QuoteServicesController::add()
  • new API:已補 Endpoint/V1/QuoteServices.php::add()
  • routing:已補 /quote_services/add.json
  • 完整搬移範圍:
    • existing session path
    • email / password / device login path
    • Google / Facebook / Facebook Limited / Apple social path
    • provider user signup、user_profilesuser_loginsapi_sessions
    • quote_services explicit insert,不用既有 row 當 template
    • quote_categories_quote_servicesnshop_service_categories
    • security_hashes / quote_services.external_review_url
    • task_event_queue.service_update,僅限 request 原本帶 QuoteService[phone_number]
    • quote_activitiesProviderSignupCreateService
    • task_queue.newQuoteService
    • event_queue.Pro_Signup
    • other category 的 quote_reports、admin email、Require_New_Category event
    • application log:newServiceScan;legacy 的 quote_services/add::POSTquote_services_add request dump 不在 endpoint 重複補,改保留 RouterV3 generic request log

前端呼叫鏈:

ProQuestionnairePopupContainer
-> questionnaireActions.submitServiceQuestionnaire()
-> APIManager.postService()
-> POST /quote_services/add.json

ServicesContainer / GoogleReservePro 也會用 postService() 新增 service。這些路徑可能送 QuoteCategory: [""];legacy 視為有效 input,會建立 service 並保存空白 category id,不建立 numeric category mapping。新版必須保留這個行為。

前端 session handling 已確認:

  • modules/actions/questionnaireActions.js 會把 response session_id 放到 tempSession
  • modules/containers/ProQuestionnairePopupContainer.js 收到 tempSession 後呼叫 auth.saveTokenIfMissing(tempSession)
  • modules/utils/user-auth.js 會寫入 session_token cookie。
  • ServicesContainer / GoogleReservePro path 是已登入 provider 新增 service,呼叫 postService() 時會用既有 session_token header;回傳主要使用 service_id

Logging 對照

legacy 在 JSON path 有五類 log:

  • CakeLog::info('add service: ...'):每次 JSON request 寫 CakePHP file log,內容是 request data。
  • LogHelper('quote_services/add::POST')->info($user_id, [api_key, request data, $_SERVER]):每次 JSON request 寫 monolog_YYYYMM
  • LogHelper('quote_services_add')->info($user_id, request data):只在 auto-signup 建立新 user 成功後寫 monolog_YYYYMM
  • LogHelper('newServiceScan')->error(...):只在 auto-signup 後 abnormal scan 例外時寫 monolog_error,不中斷新增 service。
  • $this->log(...):debug file log,包含 provided email、login success、auto signup username、user resolve result、validation fail request dump。

new project 的 PRO360\Common\LogHelper 仍寫 monolog_* table。本次經 reviewer 確認的 migration decision:request body 不在 endpoint 重複寫 full dump,因為 RouterV3 已有 generic request log。

  • RouterV3 generic request log:保留 API key、path、subDir、post data、request id;password/passwd key 由 router 層過濾。
  • 不補 quote_services/add::POST endpoint duplicate log:legacy context 會把 [api_key, request data, $_SERVER] 再寫一次,與 generic log 重複且範圍過大。
  • 不補 quote_services_add endpoint duplicate log:auto-signup request data 已在同一 request 的 generic log 中可查。
  • newServiceScan:abnormal scan 例外時寫 ERRORmonolog_error,不中斷新增 service。

legacy 的 CakeLog::info(...)$this->log(...) 是 CakePHP file log。new project 沒有 CakePHP file logging;FileLogHandler 目前是 no-op,所以這些 file log 尚未 1:1 重建。切換前若要求 literal file-log parity,需另外補新專案 file log handler 或指定可接受的替代 log channel。

上述 legacy request log 可能包含 email / phone / social token / password 類 input;new 目前只在 RouterV3 層做 password/passwd 過濾,其他敏感 token 類欄位若需額外過濾需另開 migration decision。request / service / category / queue / event 的可查狀態仍以 DB side effect 為主。

Request 重點

input規則
User[...]無 session 時用來登入或建立 provider user。支援 email/password/device、email verify code、phone verify code、referral hash、social token。
QuoteService[...]service 主資料;會正規化地址、zip、city/state/country、service_area、travel option、phone、company name;is_not_this_area=1 時保留原 full_address
QuoteCategory空 array 代表缺 category,回 error=4[""] 不算空 array,要照 legacy 允許。
QuoteCategory[]=other|...其他分類;會追加到 description,並觸發 quote report / admin email / provider email event。
QuoteService[travel_pref]非空 JSON 時依 legacy web-app 格式轉 travel flags:travel_vendor.valuetravel_customer.valueremote.value。新版也保留 flat key 相容,但主要驗證要用 legacy 巢狀格式。
travel_pref必須符合 legacy checkbox 規則:至少一個 travel option;勾距離但無 km 回 Fill Km value;填 km 但未勾距離回 Make check km check box

Legacy Rule

legacy controller:

/Users/mattsu/Documents/Site/get-lancer-docker-2/get-lancer/app/Plugin/Quotes/Controller/QuoteServicesController.php
legacy 行號職責
1009-1069add() 入口;separate user type guard、API key / session 檢查、讀 request data、初始化 response。
1071-1126解析 User[...],處理 Google / Facebook / Apple social token。
1128-1136有效 API session 時使用 session user。
1137-1174無 session 時依 email / social id / device id / password 找既有 user,必要時綁 social id。
1175-1318新 email 建立 provider user、寫 legacy quote_services_add log、profile、login、referral、CRM upsert。
1330-1344user resolve 失敗:空 password 回 error=2,密碼錯誤回 error=3
1372-1387設定 service owner、IP、top-level City / State 與 country 基本值。
1388-1415category 與 travel option validation;travel_preftravel_vendor.valuetravel_customer.valueremote.valueempty(['']) === false,所以 QuoteCategory: [""] 可通過。
1417-1438country default / ISO lookup、service_area JSON、service_cities。
1440-1460category 去重、quote_category_ids、other category 追加 description。
1462-1501phone fallback、array phone JSON encode、zip/address/full_address 處理;zip 找到 city data 時覆蓋前面 city/state/country。
1503-1535XML invalid char 過濾、公司名驗證、start_year / number_of_people 範圍。
1537-1546建立 quote_services,補 slug,建立 external review URL。
1548-1565first service 且非新 user 時更新 users.onboard_state = 1quote_service_count 由 model counter cache 更新。
1567-1574request 原本帶 QuoteService[phone_number] 時送 task_event_queue.service_update;profile phone fallback 不觸發此 queue。
1576-1589新 user 寫 ProviderSignup activity、abnormal scan、checkNewUser;scan exception 只寫 newServiceScan log,不中斷成功流程。
1591-1597CreateService activity;first service 或 new signup 送 provider welcome event。
1599-1615numeric category mapping、Nshop category、task_queue.newQuoteService
1620-1643other category:quote report、admin email、provider need-new-category event。
1646-1667success response;沒有 session 時 upsert session。
1679-1682save validation failure 回 error=4

legacy helper:

/Users/mattsu/Documents/Site/get-lancer-docker-2/get-lancer/app/Plugin/Quotes/Model/QuoteService.php
legacy 行號職責
QuoteService.php:17-32belongsTo User / City counter cache;新增 service 時會更新 users.quote_service_countcities.quote_service_count
360-389getExternalReviewUrl() / _getExternalReviewUrl():建立 security hash,組 /review/service/{service_id}/{hash} long URL,呼叫 YOURLS 轉短網址。
4828-4864sendProviderNeedNewCategoryMail():建立 Require_New_Category email event。
4866-4929sendProviderWelcomeMail():legacy email task 已註解,實際只送 MoEngage Signup_Pro
5196-5213CRM user / service methods 目前是 no-op。
6133-6137XML invalid char filter。

New PHP 8.2 對照

new 檔案行號對應 legacy職責
Lib/Common/RouterRule/Mapping.php:49-52access log pathroute /quote_services/add.jsonQuoteServices::add()
Endpoint/V1/QuoteServices.php:373-4091009-1069, 1646-1667endpoint 入口、API key guard、呼叫 user/service 建立流程、success response;service_id / user_id 在 response boundary 轉成 string;不再寫 endpoint duplicate request log。
Endpoint/V1/QuoteServices.php:1248-13771128-1174, 1269, 1330-1344session user、existing email/social/device/password login、auto-signup、error 2/3 mapping、session upsert;user lookup 不加 is_active filter,對齊 legacy User::getById() / getByEmail();不再寫 quote_services_add duplicate request log。
Lib/Common/RouterV3.php:210,266-289legacy request info loggeneric request log;password/passwd key 過濾由 router 層統一處理。
Endpoint/V1/QuoteServices.php:272-5301071-1126, 1137-1174, User.php:841-849Google / Facebook / Facebook Limited / Apple identity resolve 與既有 user social lookup / attach;social id lookup 不加 is_active filter,對齊 legacy User::getBySocialId()
Endpoint/V1/QuoteServices.php:532-7361175-1318provider user 建立;使用 explicit allowlist,不複製既有 row;無 name 時 profile fallback 用 safe username。
Endpoint/V1/QuoteServices.php:738-8291238-1307建立 profile、phone verification、referral。
Endpoint/V1/QuoteServices.php:831-9271646-1667, 1178API session upsert、safe username、generic explicit insert helper。
Endpoint/V1/QuoteServices.php:930-10131537-1546, 1548-1565, QuoteService.php:17-32建立 service、補 slug、更新 user / city counter cache、更新 user onboard、category mapping、external review URL。
Endpoint/V1/QuoteServices.php:996-10361388-1460category parse;保留 QuoteCategory: [""],只把正整數放進 numeric mapping。
Endpoint/V1/QuoteServices.php:1038-11921417-1535service 欄位 normalization、zip/address/city/service_area/company name、is_not_this_area、insert data allowlist;zip-derived city/state 會覆蓋 request city/state。
Endpoint/V1/QuoteServices.php:1194-13001376-1383, AppModel.php:101-118, SluggableBehavior.phptop-level City[name/id]State[name/id] 相容;有 id 用 id,否則依 name find-or-create。新增 city 時補 legacy Sluggable 格式 slug。
Endpoint/V1/QuoteServices.php:1302-14311388-1438travel option、model validation、service_area、country resolve。
Endpoint/V1/QuoteServices.php:1433-15611567-1643side effect:category mapping、service_update、activity、abnormal scan、welcome event、Nshop、task queue、other category。
Lib/Model/QuoteService.php:1957-1995382-388, 1537-1546建立 security hash 與 YOURLS short URL,並寫回 quote_services.external_review_url
Lib/Model/QuoteService.php:1997-20344828-4864Require_New_Category email event。
Lib/Model/QuoteService.php:2036-20824866-4929Pro_Signup MoEngage event。
Lib/Model/QuoteService.php:2085-20975196-5213, 6133-6137CRM no-op 與 XML invalid char filter。
Lib/Model/NshopServiceCategory.php:44-79NshopServiceCategory.php:13-36checkNew(),非 HK/SG 時建立缺少的 Nshop category mapping。
Lib/Model/AbnormalConfig.php:208-292, 418-541AbnormalConfig.php:307-378user / service keyword / device scan;legacy 已停用的 consumer / multi-user device scan 保留 return false。
Lib/Util/AreaUtil.php:132-143, 288-351legacy AreaUtil補 service city、zip / address helper,維持 legacy full_address prepend 行為。

現有 Method 細對狀態

quote_services/add 直接呼叫多個新專案既有 model / helper / util。這些 method 存在不代表已自動等同 legacy;review 時需逐項確認 caller、legacy 行號與 side effect。

已確認本次可改且已對齊:

範圍結論
normalizeQuoteServiceAddTravelOptions()這是 quote_services/add 專用 method。legacy 在 travel_upto 未勾時一定把 service_provider_travels_upto 清成 0;新版已同步,不再從 travel_pref JSON 保留距離。
email_verify_code social guardlegacy 只在 Google / Facebook / Facebook Limited 有驗證資料時跳過 email verify code;Apple 不會跳過。新版已同步,Apple + email_verify_code 仍會 consume verification、改用 verification email、設定 is_email_confirmed = 1
auto-signup users 欄位legacy QuoteServicesController::add() 沒有設定 mobile_app_hashmobile_app_time_modifiediphone_last_requestsecurity_question_id。新版已移除這些非 legacy 指派;mobile_app_* 維持 DB default NULLiphone_last_request / security_question_id 走 legacy 非 strict MySQL implicit value。
user lookup active filterlegacy User::getById()User::getByEmail()User::getBySocialId() 都沒有 is_active condition。新版 fetchQuoteServiceAddUserWithProfileById()fetchQuoteServiceAddUserWithProfileByEmail()fetchQuoteServiceAddUserBySocialIdentity() 已移除 u.is_active = 1,避免 inactive user path 與 legacy 不一致。

Migration decision:

  • 既有 user 用 Google id token 登入且原本沒有 google_user_id 時,legacy 會呼叫 QuoteUtil::update_user_google_link($user_id, $google_auth_data)
  • legacy helper 會更新 google_access_token,但呼叫端沒有傳第三個 $id_token,所以實際會把既有 users.google_access_token 清成空字串。
  • new 不照搬這個清空 token 的 side effect。quote_services/add 的責任是新增 service 與必要 social linkage;保留既有 google_access_token 可避免新增 service 流程破壞其他 Google token 使用情境。
  • new 仍對齊必要 linkage 欄位:is_google_connected = 1google_user_idgoogle_avatar_url

auto-signup user 欄位對照原則:

  • legacy 明確指派:role_idusernameemailpassworddevice_idis_activeonboard_stateip_id、social register / connected 欄位、email verification 欄位、entranceregistration_sourceiphone_last_accesslast_access_clientmulti_lang_id
  • new explicit allowlist 只可補 legacy/Cake/DB 會產生的等價 default,例如 counter 0、nullable 欄位 NULL、非 strict MySQL 會落出的空字串 / zero date。
  • 不可加入其他註冊流程才有的欄位,例如 mobile_app_hash

已查 shared method,暫不改共用行為:

new methodlegacy 對照結論
ApiSession::upsert()legacy ApiSession::upsert()兩邊皆依 user / device upsert session、更新 expires、寫 api_keyapi_session log。new 只多 PHP 8.2 / CLI fallback;此 method 有其他 caller,不直接改 shared behavior。quote_services/add 透過 upsertQuoteServiceAddSession() 補齊 legacy 需要的 API key server value。

仍需在後續 review 細對,不可因已呼叫 existing method 就視為完成:

類型method / source要確認
Social authPro360LoginUtil::verify_google_id_token()verify_fb_access_token()FacebookUtil::verify()AppleVerifyLogtoken 驗證來源、Android fallback、user lookup / social id attach、錯誤時是否繼續走 signup。
Email / phone 驗證EmailVerification::getByVerifyCode()/consume()PhoneVerification::afterAuth()PhoneUtilFactory::get()User::runResetPhoneConfirm()verify code consume、phone normalize、confirmed flag、log / side effect。
Referral / metadataSecurityHash::getByHash('UserReferral')UserReferral::addReferral()ApiKey::getClientByKey()MultiTranslation::getHeaderLangId()Ip::toSaveIp()referral 寫入、registration source、language、IP 欄位是否與 legacy 一致。
Service helperServiceCertificate::isCompanyConfirmed()QuoteService::getExternalReviewUrl()sendProviderWelcomeMail()sendProviderNeedNewCategoryMail()公司名驗證、短網址、MoEngage / email event payload 與 config。
Queue / email / reportTaskEventQueueTaskQueueEmailAdminListEmailQueueNshopServiceCategory::checkNew()AbnormalConfig::newServiceScan()queue key、payload、worker log、error path 是否中斷主流程。

已知需留意:new sendProviderNeedNewCategoryMail() 目前有 empty email guard;legacy helper 沒有這個 guard。此 method 也被 shared 使用,目前 /Users/mattsu/Documents/Site/web-app 未看到 quote_services/addother|... category,所以先不改 shared helper。若 staging 測到 add 的 other category 且 email 可為空,需補 add 專用相容處理。

Legacy HTML View-only 行為

legacy QuoteServicesController::add() 在 user resolve 成功後會查 Sudopay payout 連接狀態:

QuoteServicesController.php:1354-1368

這段只設定 is_payout_erroruserDetailsstepQuoteServices/add.ctp 使用;JSON success response 沒有輸出這些欄位。Sudopay::isHavingPendingGatewayConnect()Sudopay::GetUserConnectedGateways() 只讀 sudopay_payment_gateways / sudopay_payment_gateways_users,不寫 DB。

2026-06-12 正式機 log 只看到 /quote_services/add.json,未看到 legacy HTML path /quote_services/add/quote_services/add/...。因此新版 API 不搬這段 Sudopay view state;這是基於流量與 JSON response 對照,不是漏檢。若未來要重建 legacy HTML 新增服務頁,這段需另列為頁面行為搬移。

前端目前 /Users/mattsu/Documents/Site/web-app 的 add 呼叫主要送 QuoteService[travel_pref] JSON:

  • ProQuestionnairePopupContainer -> APIManager.postService()
  • ServicesContainer.handleNewService() -> APIManager.postService()
  • GoogleReservePro -> APIManager.postService()

目前未看到這些前端路徑送 legacy checkbox 欄位 travel_upto;但 API 仍保留舊客戶端的 checkbox validation 與距離清除規則。

DB Side Effect

成功時可能寫入或更新:

  • users
  • cities
  • user_profiles
  • user_logins
  • api_sessions
  • phone_verification_logs
  • user_referrals
  • quote_services
  • security_hashes
  • quote_categories_quote_services
  • nshop_service_categories
  • task_event_queue
  • quote_activities
  • abnormal_check_items
  • task_queue
  • event_queue
  • quote_reports
  • email_queue

驗證時不要只看 quote_services;至少要依情境確認 service row、category mapping、activity、queue/event,以及新 user path 的 user/profile/session。

other category path 的 quote_reports 要對齊 legacy:

  • type = need_category
  • reported_user_id = user_id
  • message = {user_email}, {other_category_1}, {other_category_2}
  • admin email subject / body 都是 Pro signup other categories: {message}

Staging Legacy 實測

2026-06-12 以 legacy staging 呼叫:

POST /quote_services/add.json
QuoteService[business_name]=longlong
QuoteService[travel_pref]={"travel_vendor":{"value":"1"},"travel_customer":{"value":"1"},"remote":{"value":"1"}}
QuoteService[full_address]=台北市中山區中正路1號
QuoteService[zip_code]=104
QuoteService[country_id]=222
QuoteCategory[0]=

response:

{"error":0,"service_id":"13664","session_id":"baigkocqghnqpu60bipc5ork97","user_id":"8617"}

DB / log 重點:

  • legacy staging monolog_202606.channel = quote_services/add::POST 保留原始 request,確認 QuoteCategory = [""]。這是歷史 legacy 驗證資料;new 不再補同名 endpoint duplicate log。
  • quote_services.id = 13664 建立成功,slug = 861713664external_review_url = https://p36.net/sw/QFUWt,並建立 security_hashes(model=QuoteService, foreign_id=13664)
  • request 沒有 QuoteService[address],legacy 保留傳入的 full_address = 台北市中山區中正路1號address = "";只用 zip_code = 104city_id = 360state_id = 5466country_id = 222
  • legacy QuoteService model 透過 counter cache 更新 users.quote_service_countcities.quote_service_count;新版需明確更新,不能只寫 service row。
  • phone_number 未傳時 fallback 到 user_profiles.phone = 0908189361
  • travel flags 依 travel_pref 設為三個 1service_provider_travels_upto = 0
  • 建立 quote_activities.quote_activity_type_id = 25 (Create Service)。
  • request 沒有 QuoteService[phone_number],未建立 task_event_queue.service_update
  • worker 後續建立 event_queue_log_4.event_key = New_Service_Match;因此驗 notification 要查 queue-or-log。

注意:這筆 service 後續又被前端流程更新過。09:34:46 出現 Update Service activity,並把 quote_category_ids / quote_categories_quote_services 改成 category 346。因此目前 DB 上的 quote_category_ids = 346 不是 add.json 初始結果;判斷 add 初始 category 要以當時 legacy request log 與 update 前後時間線為準。new 切換後改看 RouterV3 generic request log 或測試時保存的 request body。

Config / 部署檢查

切 staging / production 前需確認實際部署 config,不可只看本地 legacy checkout:

area 是非台灣環境開關,不是台灣設定。台灣環境不可補 ProConfig::$config['area'] = 'tw';只有確認部署為 sg / hk 等非 TW 環境時才設定 areasys_area = tw 不能當成要補 area = tw 的依據。

key / source用途
site.urlexternal review long URL。
yourls.api_url, yourls.api_keyservice 建立後產生 short URL。
email.signatureother category provider email。
mail.icon.help_centerRequire_New_Category template replacement;new 有 fallback URL,但部署環境仍應同步。
mail.icon.resume_instructionRequire_New_Category template replacement;new 有 fallback URL,但部署環境仍應同步。
newCategoryEmailListother category admin email list。
EmailTemplate.from_emailother category admin email 的 default from。
API key client/platform settingregistration_sourcelast_access_client、Google token Android fallback。
Google / Facebook / Apple login settingssocial signup / login path。
site.is_enabel_freelancer_user_typelegacy 入口有 guard;legacy schema 預設 0。new 目前沒有同名設定,切換前需確認實際 production 仍為 false;若 production 為 true,需補 role guard。

QuoteService::getExternalReviewUrl() 會實際呼叫 YOURLS;本機 PHPUnit 不直接打 full endpoint,以免依賴外部短網址服務。部署後仍需用 staging curl + DB 查驗 full path。

PHP 5.6 -> 8.2 注意

  • 不可把 nullable input 直接丟給 trim()mb_strpos()json_decode() 後假設一定是原型態;新版 helper 先做 cast / type guard。
  • 不可用 SELECT * FROM quote_services LIMIT 1 當 insert template;新版對 usersquote_services、mapping/report 類 insert 都使用 explicit allowlist。
  • travel_pref 非空時 legacy 會跳過 checkbox validation;新版也維持這個分支,但用 type guard 避免 PHP 8.2 nullable / property warning。
  • full_address 是否重組看 request 的 is_not_this_area,不是看部署 areais_not_this_area=1 時要保留前端傳入值。
  • QuoteCategory: [""] 是 legacy 可通過的 array,不可改成空 category error。
  • Facebook Limited login 的 legacy profile name fallback 最終會落到 safe username;新版維持可見結果,不用完整 email,也不改用 Facebook Limited name。

測試

php -l Endpoint/V1/QuoteServices.php
php -l Lib/Model/QuoteService.php
php -l Lib/Model/NshopServiceCategory.php
php -l Lib/Model/AbnormalConfig.php
php -l Lib/Util/AreaUtil.php
docker exec -w /project-data cd63f9147e8d ./vendor/bin/phpunit tests/QuoteServicesAddTest.php

目前結果:

OK (17 tests, 59 assertions)

測試覆蓋:

  • success response 的 service_iduser_id 維持 legacy JSON string 型別,並以 assertIsString() 防止 regression。
  • QuoteCategory: ["", "123", "other|測試分類"] 會保留空白 category id,只把 123 放入 numeric mapping,other category 另行記錄。
  • service normalization:zip/address/city/service_cities、phone fallback、other category 追加 description、legacy 巢狀 travel_pref flags。
  • top-level City[name/id]State[name/id] 相容;新增 city slug 維持 legacy Sluggable 格式;zip-derived city/state 會覆蓋 request city/state。
  • 沒有送 QuoteService[address] 時,即使有 zip_code 也保留傳入的 full_address;有送 address 時才照 legacy 重組 full_address
  • legacy model counter cache 會更新 users.quote_service_countcities.quote_service_count;新版已補 explicit counter update。
  • service_update queue 只看 request 是否原本帶 QuoteService[phone_number];profile phone fallback 不觸發。
  • travel_upto 未勾時,service_provider_travels_upto 會照 legacy 清成 0,即使 request / travel_pref 帶了距離。
  • email_verify_code social guard 對齊 legacy:Google / Facebook / Facebook Limited 會跳過;Apple 不會跳過。
  • auto-signup user insert method 不可包含 legacy 未指定的高風險欄位:mobile_app_hashmobile_app_time_modifiediphone_last_requestsecurity_question_id
  • legacy validation message:缺 category、缺 travel option。
  • is_not_this_area=1 時保留原 full_address,不套台灣地址重組。
  • 新 user 無 User[name] 時,profile first name fallback 使用 safe username,不使用完整 email。
  • Facebook Limited signup 無 User[name] 時,profile first name 也維持 legacy fallback。

待 staging 驗證

部署後需補一筆完整 curl / DB 實測,至少覆蓋:

  • logged-in provider 新增 service。
  • QuoteCategory: [""] 新增 service。
  • numeric category mapping。
  • other category side effect。
  • 新 email signup path。