GET /quote_feedbacks/sent/sort:{sort}.json

文件狀態:本地 legacy parity 實作與測試完成,待部署 new staging 複驗
最後更新:2026-07-24

快速結論

  • query、nested data、排序、response shape 與 JSON 型別已對齊 legacy。
  • auth 使用 endpoint 專用流程,保留 legacy Redis cache、blocked check、access update 與 returned-service queue 時機;不呼叫 shared session validator。
  • invalid session 保留 legacy warning log,但不多送 legacy 沒有的 expired Set-Cookie
  • Docker PHP 8.2 測試為 10 tests, 76 assertions;new staging 尚待部署後複驗。

目的

將目前仍由 legacy 處理的「使用者送出的評價」查詢搬到 pro360_api_82,預設要求 legacy parity。

實際常用路徑:

GET /quote_feedbacks/sent/sort:time.json

保留路徑:

GET /quote_feedbacks/sent.json
GET /quote_feedbacks/sent/sort:.json
GET /quote_feedbacks/sent/sort:score_high.json
GET /quote_feedbacks/sent/sort:score_low.json

前端觸發方式

Staging Web 操作路徑:

  1. 登入後切到「案主/消費者模式」。
  2. 開啟「帳戶設定 → 評價紀錄」,或直接進入 /dashboard/settings/reviews
  3. 頁面載入時會呼叫 GET /quote_feedbacks/sent/sort:time.json
  4. 切換排序選單時,會改呼叫 sort:score_highsort:score_low

專家模式不會顯示「評價紀錄」選單,需要先切回案主模式。

Staging 帳戶設定的評價紀錄頁面,Network 顯示 quote_feedbacks sent API

圖:2026-07-24 staging 前端觸發方式;切換成「最低至最高分」後,Network 收到 sort:score_low.json,HTTP 200。此圖只證明 caller 與操作路徑,不代表 new endpoint 已部署。

流量證據

2026-07-24 查詢 legacy get-lancer_access.log* 保留區間:

  • GET /quote_feedbacks/sent...:213 次,皆為 HTTP 200。
  • OPTIONS /quote_feedbacks/sent...:64 次,皆為 HTTP 200。
  • 絕大多數為 sort:time;另有 sort:score_high
  • caller 包含 consumer iOS App 與 www.pro360.com.tw Web。

Legacy 實際路徑

Controller

  • 檔案:get-lancer-php56/app/Plugin/Quotes/Controller/QuoteFeedbacksController.php
  • action:sent(),第 38–128 行。
  • session guard:第 41–43 行。
  • sort mapping:第 45–59 行。
  • feedback query:第 61–96 行。
  • service photo / feedback attachment mapping:第 98–121 行。
  • raw array response:第 123 行。

Shared helper / model

  • get-lancer-php56/app/Controller/RestApiHelper.php
    • hasValidApiSession():第 75–153 行。
    • getUserId() 與 legacy request info log:第 162–182 行。
  • get-lancer-php56/app/Plugin/Acl/Controller/Component/AclFilterComponent.php
    • invalid-session warning 與 403 response:第 30–64 行。
  • get-lancer-php56/app/Plugin/Quotes/Model/QuoteFeedback.php
    • QuoteFeedbackComment association:第 57–65 行,依 id 排序。
  • get-lancer-php56/app/Plugin/Quotes/Model/QuoteFeedbackAttachment.php
    • getAttachmentMap():第 12–29 行,只取 is_active = 1 的 thumb URL。
  • get-lancer-php56/app/Controller/AppController.php
    • handleProApiException():第 1320–1343 行。

New 對應

  • route:pro360_api_82/Lib/Common/RouterRule/Mapping.php 第 152–155 行。
  • endpoint:pro360_api_82/Endpoint/V1/QuoteFeedbacks.php
    • sent() 與 error mapping:第 24–59 行。
    • legacy session/cache 與 side effects:第 61–145 行。
    • legacy info/warning log:第 147–182 行。
  • model:pro360_api_82/Lib/Model/QuoteFeedback.php
    • getLegacySentFeedbacks():第 30–100 行。
  • test:pro360_api_82/tests/QuoteFeedbacksSentTest.php
    • setup / cleanup:第 59–127 行。
    • parity cases:第 129–421 行。
    • fixtures / dispatch helpers:第 423 行起。

既有 QuoteFeedback::getFormatFeedbackByUserId() 不可直接重用:它會改以服務擁有者查評價、過濾 abnormal feedback、查 User、隱藏姓名、解析 tag 並改變 comment shape,與本 API 不同。為避免影響既有 caller,本 API 使用專用 method。

逐段對照

階段LegacyNew結論
guardAPI key、Redis session cache、session/user、cache miss blocked checkendpoint 專用 resolveLegacySentUser(),不呼叫 shared validator已對齊
inputnamed parameter sort,包含空值route 映射到 sort param,接受 sort:.json已對齊
sorthigh / low;其餘一律 modified DESC專用 allowlist mapping已對齊,沒有把 input 拼成任意 SQL
feedback queryuser condition、bid LEFT JOIN、service INNER JOINgetLegacySentFeedbacks() 相同 join / condition已對齊
nested querycomments、service photo、active feedback attachments專用 comments / photo mapping,重用 attachment active filter已對齊
DB write無 domain write無 domain write已對齊
payment不適用不適用
auth side effectcache miss 才更新 access、檢查 blocked/returned service、寫 15 分鐘 cacheendpoint 專用實作相同時機;不寫 multi_lang_id / daily activity已對齊
request logsuccess info;invalid session warningendpoint-specific LogHelper已對齊並實際查 log
responseraw arrayraw array已對齊
config無 endpoint-specific key無 endpoint-specific key不需部署設定

Guard 與 input

項目LegacyNew 目標
request format.jsonRequestHandler->prefers('json')mapping 僅接受 .json
API key必須有效相同;失敗 HTTP 401
session必須能取得有效 user相同;失敗 HTTP 403
user只從 session 取得,不接受 request user id相同
users.is_activelegacy 沒有此 guard不新增 active guard
sort=score_highis_satisfied DESC相同
sort=score_lowis_satisfied ASC相同
sort=time、缺少、空字串或未知值modified DESC相同
tie-breaker不可自行新增

Staging legacy 於 2026-07-24 實測:

  • missing session:HTTP 403,{"error":99999,"message":"invalid_user"}
  • invalid API key:HTTP 401,{"error":99999,"message":"INVALID API KEY"}

Query parity

主查詢:

  • quote_feedbacks.user_id = session user id
  • LEFT JOIN quote_bids,且 join condition 必須同時包含:
    • QuoteFeedback.model = 'QuoteBids'
    • QuoteFeedback.foreign_id = QuoteBid.id
  • INNER JOIN quote_servicesQuoteFeedback.quote_service_id = QuoteService.id
  • 不加 is_publicis_rejectedis_active、abnormal feedback 或其他 visibility filter。
  • 不分頁、不設 limit。

額外查詢:

  • quote_feedback_comments:回傳全部欄位,不加 is_customer 等 filter,依 id ASC
  • quote_feedback_attachments:只取 is_active = 1,回傳每筆 feedback 的 thumb URL array。
  • attachments:只取 class = 'QuoteService' 與命中的 service id,映射 amazon_s3_thumb_url;不加 review_status filter。

Response contract

成功 response 是 raw JSON array,不包 errormessagedata

每個 item 順序與 shape:

{
  "QuoteService": {
    "business_name": "string",
    "photo_url": "string|null"
  },
  "QuoteFeedback": {},
  "QuoteBid": {
    "quote_request_id": "string|null"
  },
  "QuoteFeedbackComment": [],
  "FeedbackAttachment": []
}

重要型別與 negative contract:

  • QuoteFeedbackquote_feedbacks 全欄位;DB id、flag、score 維持 JSON string,DB null 維持 null。
  • QuoteFeedback.tag 保持 DB 原始字串或 null,不做 json_decode()
  • QuoteFeedbackComment 每列保留全部欄位,包括 quote_feedback_idis_customer
  • 不回 UserUserProfile、category title 或 Nshop formatter 欄位。
  • QuoteBids model 或找不到對應 bid 時,QuoteBid.quote_request_id 為 null;feedback 本身仍可回傳。
  • 找不到對應 quote_services row 的 feedback 因 legacy INNER JOIN 不回傳。

2026-07-24 staging legacy baseline:user 8617,回傳 3 筆;已確認 comment、feedback attachment、service photo、排序及上述 JSON 型別。

Side effects、log 與 config

Domain 查詢本身不寫入 feedback、comment、attachment、service 或 bid table,也沒有 payment 或 notification。

Legacy auth 行為:

  • Redis key:api_session_{session_id},TTL 15 分鐘。
  • cache hit:直接使用 cached user,不重查 blocked user、不更新 access。
  • cache miss:查 session/user、檢查 blocked user;超過 180 天的 provider 可能新增 task_queue.action=returnedQuoteService
  • cache miss 只更新 users.iphone_last_accesslast_access_client;不更新 multi_lang_id,也不寫 daily access activity。
  • legacy 沒有 users.is_active guard。
  • session 不存在時只清除本次 PHP process 的 cookie 值,不送 expired Set-Cookie header。

Legacy RestApiHelper::getUserId() 會寫 request info log:

  • channel:實際 request path 加 ::GET
  • message:session user id。
  • context:至少有 api_key;有 request data / form 時才加入 data / form

New 必須保留上述 endpoint-specific log;RouterV3 generic request log 不能取代它。

Legacy ACL 在 invalid session 時另寫 warning:channel 為實際 path 加 ::GET、message 為 got 403、context 為原始 API key 與 session-token header。這會把 credential 寫入 log;目前依 legacy parity 保留。若要遮罩,必須另做 reviewer 明確接受的 migration decision。

本 API 沒有新增 deployment config、URL、template 或 payment key,也不再透過 invalid-session path 讀取 api.url 產生 Cookie header。returnedQuoteService 使用既有 task_queue

測試矩陣

  • 有效 session、無 feedback:回 []
  • time、缺少 sort、sort: 空字串、未知 sort:modified DESC
  • score_high / score_low:只按 score 排序,不加 tie-breaker。
  • nested comment 回完整欄位且依 id 排序。
  • active feedback attachment 回 thumb array;inactive attachment 不回。
  • service photo 有值 / 無值。
  • QuoteBids join 出 quote_request_id;非 QuoteBids 為 null。
  • orphan service feedback 被 inner join 排除。
  • abnormal feedback 不可被額外排除。
  • raw tag 不可被 decode。
  • missing session / invalid API key response 與 HTTP status。
  • invalid session warning 保留 path、API key 與原始 session token;不呼叫 shared cookie remover。
  • inactive session user 不可被 new-only active guard 擋掉。
  • blocked user 回 legacy HTTP 403 與 Blocked User body。
  • cache miss 只更新 legacy access 欄位並可新增 returned-service queue;cache hit 不重複更新。
  • endpoint-specific log 保留完整 sort path、user id message 與 api key context。
  • 呼叫前後 domain table row count 不變。

驗證結果

  • Docker PHP 8.2.29 lint:route、endpoint、model、test 全部通過。
  • PHPUnit:10 tests, 76 assertions,全部通過;涵蓋 auth cache、side effects、invalid-session warning 與空 sort route。
  • legacy staging user 8617 baseline 與本地 model output 以 canonical JSON 比對:timescore_highscore_low、未知 sort 的結構、值、array order 與 JSON 型別一致;不宣稱 raw bytes 相同。
  • local monolog 實查:channel 保留完整 path,例如 quote_feedbacks/sent/sort:score_low.json::GET;message 為 user id;context 為 api_key
  • invalid-session warning 實查:monolog_warning 的 path、got 403 與 context 符合 legacy。
  • 測後 fixture 清理確認:users、api_sessions、quote_services、blocked_users、queue 與 Redis session cache 均無殘留。

部署後待驗證

  • 部署 new staging 後,以同 user / session 比對 raw response 與 JSON 型別。
  • 切換 timescore_highscore_low,確認畫面排序與 HTTP 200。
  • 查 new staging monolog,確認 endpoint-specific 與 RouterV3 generic log 都存在,且完整 path 沒被 rewrite 截短。
  • 確認 domain table 無新增 / 更新;cache miss 只出現文件列出的 legacy auth side effects。