# Mataan > 公司內部的專案管理系統:議題追蹤、測試案例、上版管理、合約時程與對客戶的文件往返, > 掛在「客戶 → 專案」的樹上。網頁需要 Google 登入;AI 代理與 CLI 工具改用 REST API, > 以個人 API token 認證,沒有 token 時走一次性裝置授權(使用者在瀏覽器按一下核准)。 這份說明公開、不需要登入,給 AI 代理與 CLI 工具讀。 - 完整端點對照(含每個欄位,由原始碼自動產生): - 網站: - API base:`https://mataan.anyong.com.tw/api`(只有這一個環境,就是正式環境) **Mataan 只有 Google 登入,沒有帳號密碼。不要向使用者索取密碼**,也不要叫他去複製瀏覽器的 localStorage——照下面「認證」一節走裝置授權,使用者只需要在瀏覽器按一次「授權」。 ## 拿到一個網頁網址時 網頁是單頁應用程式,直接抓網址只會拿到空殼。把網址換成對應的 API: | 網頁網址 | 是什麼 | 改打這支 API(都要加 `/api` 前綴) | | ---------------------------------- | -------------------------- | ---------------------------------------------------------------------- | | `/issues/E1-16` | 一張議題(以編號) | `GET /issues/by-key/E1-16`,拿到 `id` 之後再查留言、測試案例等子資源 | | `/issues/` | 一張議題(以 id) | `GET /issues/` | | `/folders/` | 客戶或專案頁 | `GET /folders/`;現況摘要 `GET /folders//status` | | `/releases/` | 一次上版 | `GET /releases/`、`GET /releases//test-cases` | | `/my-work` | 我的工作 | `GET /my-work` | | `/issues` | 跨專案的議題清單 | `GET /issues/assigned-to-me`(可帶 `status`、`keyword` 等篩選) | | `/calendar` | 月曆 | `GET /calendar` | | `/diagram/` | 架構圖 | `GET /diagrams/` | 議題編號的格式是「專案代碼-流水號」(`E1-16`、`YT-42`),**不要自己編號碼**,查不到就照實說。 全站搜尋是 `GET /search?q=關鍵字`,限定某個客戶或專案是 `GET /folders//search?q=關鍵字`。 ## 認證 每個請求帶個人 API token(前綴 `mtn_`): ```bash curl -H "Authorization: Bearer $MATAAN_TOKEN" https://mataan.anyong.com.tw/api/auth/me ``` 沒帶或帶錯會回 401,回應裡的 `auth` 欄位與 `Link: <…/llms.txt>; rel="help"` header 都指回這份文件。 Token 帶的是**那個人自己的權限**,不是特權通道:看不到的客戶一樣 403,每次呼叫都記在他名下的稽核紀錄。 有幾件事 token 刻意做不到,要本人在瀏覽器裡操作(回 403):讀憑證密碼與 TOTP、管理 API token 本身、 核准裝置授權(不能自己核准自己)。 ### 本機沒有 token:一次性裝置授權 `$MATAAN_TOKEN` 沒設時**直接跑下面這段**,把印出來的網址交給使用者、請他按授權,然後等它自己完成。 ```bash mataan_authorize() { local base=https://mataan.anyong.com.tw/api local start; start=$(curl -s -X POST $base/auth/device/start \ -H 'Content-Type: application/json' \ -d "{\"clientName\":\"$(hostname)\",\"expiresInDays\":90}") local device_code user_code url interval device_code=$(echo "$start" | jq -r .deviceCode) user_code=$(echo "$start" | jq -r .userCode) url=$(echo "$start" | jq -r .verificationUriComplete) interval=$(echo "$start" | jq -r .interval) echo "請開啟 ${url} 並確認授權碼 ${user_code},按下「授權」" >&2 local deadline=$((SECONDS + 600)) while (( SECONDS < deadline )); do sleep "$interval" local poll; poll=$(curl -s -X POST $base/auth/device/poll \ -H 'Content-Type: application/json' -d "{\"deviceCode\":\"$device_code\"}") case "$(echo "$poll" | jq -r .status)" in approved) echo "$poll" | jq -r .token; return 0 ;; denied) echo "使用者拒絕了授權" >&2; return 1 ;; expired) echo "授權請求已逾期,請重新開始" >&2; return 1 ;; esac done echo "等待授權逾時" >&2; return 1 } export MATAAN_TOKEN=$(mataan_authorize) || return ``` - 授權碼 10 分鐘內有效;核准之後 token 只能領一次。誰核准,token 就掛在誰身上。 - 要留給下次用,寫進 shell 設定或密鑰管理工具。**不要寫進專案裡的檔案**,會被 commit 出去。 - 另一條路:使用者自己到 最下面的「API token」建立並複製。 ## 概念 ### 客戶與專案 資料是一棵樹,層級決定意義: | 層級 | 是什麼 | 掛在這裡的東西 | | ------------- | ----------- | ------------------------------------------------------ | | 第 1 層 | 客戶/產品 | 成員與職能、上版檢查清單模板、小工具、Slack 頻道綁定 | | 第 2 層 | **專案** | 議題、上版、合約階段與請款、文件往返、整合測試案例模板 | 早期有些議題直接掛在第 1 層,所以查議題時兩層都可能有。`GET /folders/projects` 依「客戶 → 其下專案」 列出所有專案,最外層項目的 `environment` 為 `null`。 ### 議題 狀態照順序走,**不要跳步**: | 狀態 | 意思 | | ---------------- | ---------------------------------------------------------- | | `open` | 待處理 | | `in_progress` | 處理中 | | `ready_for_test` | **可測試**:執行人員做完就改這個,交給驗證人員驗 | | `resolved` | 已解決:驗證人員驗完、沒問題 | | `closed` | 已關閉:**不用做了**,不是驗完的下一步 | 開發完成是 `ready_for_test`,不是 `resolved`。 指派分兩種角色,都是使用者 id 的陣列:`executorAssigneeIds`(執行人員,常同時掛前後端)與 `verifierAssigneeIds`(驗證人員)。**陣列是整組取代**,要加人就把原本的一起帶上。 類型怎麼選: | 類型 | 判準 | | ------------- | -------------------------------------------- | | `feature` | 合約或需求裡的新功能 | | `maintenance` | 計畫性的工作,時間是自己排的 | | `bug` | **上線後**才發現的缺陷 | | `security` | 觸發點是安全性;跟安全有關就選它,即使是計畫性的 | | `incident` | 非計畫性,服務已經受影響 | | `todo` | 不屬於以上、要記下來別忘的事 | | `document` | 建立或更新文件 | 優先度在畫面上叫 P0–P3,API 的值是: | 值 | 畫面 | 定義 | | -------- | ---- | ---------------------------------------------------------- | | `urgent` | P0 | 服務中斷或核心功能無法使用、沒有替代做法,立刻處理 | | `high` | P1 | 主要功能受影響或有明確時程壓力,有暫時的替代做法,優先排入 | | `medium` | P2 | 一般需求與缺陷,照正常排程處理(沒特別說就用這個) | | `low` | P3 | 影響很小,有空再做(文案、樣式微調、改善建議) | `bug` 另有 `bugSource`(`customer` 客戶/`pm` PM/`qa` QA 回報)與 `bugCause` (`spec_missing` 需求沒寫/`spec_covered` 需求有寫)。 `maintenance`/`incident`/`security` 多四個欄位:`occurredAt`(發生時間)、`environmentId`、 `impact`(影響範圍與後續措施)、`opsCategory`(`routine_check`/`infrastructure`/`database`/ `network_cert`/`account`/`monitoring`/`deployment`)。 `storyPoint` 是字串數值(`"3"`、`"0.5"`),不評估就給 `null`。 `description` 與留言的 `body` 是 HTML(`

…

`);純文字也收。 **議題寫使用者看得到的行為,不寫實作手段**:標題給 PM 與 QA 看—— 「議題列表可以用狀態篩選」,不是「重構 IssuesService 的查詢邏輯」。 一張議題=一個 QA 能獨立驗收的行為(前後端同一張),技術細節放 `description`。 ### 測試案例 掛在議題或上版底下。狀態: | 狀態 | 意思 | | --------- | ---------------------------------- | | `pending` | 未測 | | `passed` | 通過 | | `failed` | 異常(會通知該議題的執行人員) | | `fixing` | 修正中,執行人員處理中 | | `retest` | 待重測,改好了請測試人員再測一次 | | `invalid` | 誤判,不需處理 | 算「已結案」的是 `passed` 與 `invalid`。 ### 上版 一次上版有多個環境(客戶的測試機、正式機…),**整體狀態是各環境彙總出來的,不能直接改**—— 要改的是 `release-environments`。環境狀態:`pending`/`in_progress`/`released`/`cancelled`。 - 標成 `released` 之前要先核准:`POST /release-environments/:id/approve`。同一人可以連續做完。 - 已上線改回其他狀態=回滾,必須在 `note` 填原因。 - 上版名稱留空時,後端以最早的排定日期自動命名。 - `PUT /releases/:id` 的 `environments` 與 `issueIds` 是**整批取代**。 ### 合約時程與文件往返 專案上的一次性日期直接是資料夾欄位(`PATCH /folders/:id`):`quotationSignedDate` 報價單簽回、 `kickoffDate` 起案、`kickoffMeetingAt` 啟案會議(**只有這項帶時間**,ISO 8601)、 `uatStartDate`/`uatEndDate`、`warrantyStartDate`/`warrantyEndDate`、`closedDate`, 其餘都是 `YYYY-MM-DD`。 **先行啟案**=合約還沒簽回、人已經在做。填 `preContractStartDate` 就開始追蹤, 等 `quotationSignedDate` 一填就自動結束。相關欄位:`preContractCommitment` (`email`/`meeting`/`verbal`/`other`)、`preContractCommittedAt`、`preContractCommittedBy` (客戶端具名的人)、`preContractEvidenceUrl`、`preContractExpectedSignDate`、`preContractNote`。 把 `preContractStartDate` 清成 `null` 會連同整組一起清掉。 **文件往返**是一列一次「發出 → 簽回」(`/folders/:id/documents`)。種類 `kind`:`quotation` 報價單/ `spec` 需求規格文件/`design` 設計稿/`change_request` 變更單/`acceptance` 測試報告及驗收文件/ `other`。`signedAt` 為 `null` 就是還在客戶那邊。**改版再寄是新增一筆、帶 `supersedesId` 指向前一版**, 不是去改舊那筆——中間發過幾版、客戶各壓了多久正是事後要講的話;只有補簽回日、修錯字才用 `PUT`。 ## 常見操作 以下都假設已經 `export MATAAN_TOKEN=…`,並以 `$API` 代表 `https://mataan.anyong.com.tw/api`。 ```bash API=https://mataan.anyong.com.tw/api H="Authorization: Bearer $MATAAN_TOKEN" ``` **讀一張議題的全部內容** ```bash ISSUE=$(curl -s -H "$H" $API/issues/by-key/E1-16) ID=$(echo "$ISSUE" | jq -r .id) echo "$ISSUE" | jq '{key, title, status, type, priority, description}' curl -s -H "$H" $API/issues/$ID/comments | jq '.[] | {author: .author.name, body, createdAt}' curl -s -H "$H" $API/issues/$ID/test-cases | jq '.[] | {content, status}' curl -s -H "$H" $API/issues/$ID/activities ``` **找專案、列出還沒做完的議題** ```bash curl -s -H "$H" $API/folders/projects | jq '.[] | {id, name, client: .environment.name}' curl -s -H "$H" "$API/folders/$FOLDER/issues?status=open&status=in_progress&status=ready_for_test&pageSize=50" ``` 列表回傳 `{ items, total }`。篩選:`status`、`type`、`opsCategory`、`labelId`(重複同名參數=多選)、`priority`、`assigneeId`、 `keyword`(標題+說明,空白分隔任一詞符合)、`page`、`pageSize`、`sortBy`、`sortDirection`。 **開一張 BUG 並指派**(先查有沒有人開過) ```bash curl -s -H "$H" "$API/folders/$FOLDER/issues?keyword=優惠券&status=open&status=in_progress" | jq '.items[] | {key, title}' EXECUTOR=$(curl -s -H "$H" "$API/users/search-list?q=alice" | jq -r '.[0].id') curl -s -X POST -H "$H" -H 'Content-Type: application/json' $API/folders/$FOLDER/issues -d "{ \"title\": \"過期優惠券仍可套用\", \"type\": \"bug\", \"priority\": \"high\", \"bugSource\": \"customer\", \"description\": \"

重現步驟…

\", \"executorAssigneeIds\": [\"$EXECUTOR\"] }" | jq '{key, id}' ``` **做完了:改成可測試並留言** ```bash curl -s -X PUT -H "$H" -H 'Content-Type: application/json' $API/issues/$ID -d '{"status":"ready_for_test"}' > /dev/null curl -s -X POST -H "$H" -H 'Content-Type: application/json' $API/issues/$ID/comments -d '{"body":"

已修好,請驗證

"}' > /dev/null ``` **加測試案例並標記結果** ```bash TC=$(curl -s -X POST -H "$H" -H 'Content-Type: application/json' $API/issues/$ID/test-cases \ -d '{"content":"使用過期優惠券時顯示「優惠券已過期」"}' | jq -r .id) curl -s -X PATCH -H "$H" -H 'Content-Type: application/json' $API/test-cases/$TC -d '{"status":"passed"}' > /dev/null ``` **專案現況** ```bash curl -s -H "$H" $API/folders/$FOLDER/status # { unfinishedIssueCount, issuesByStatus, unresolvedTestCaseCount, ongoingReleases, sampled } ``` `issuesByStatus` 只取樣前 500 張,超過時 `sampled` 為 `true`(`unfinishedIssueCount` 仍是全量)。 **建立上版並標記上線** ```bash ENV=$(curl -s -H "$H" $API/folders/$CLIENT/client-environments | jq -r '.[0].id') REL=$(curl -s -X POST -H "$H" -H 'Content-Type: application/json' $API/folders/$FOLDER/releases -d "{ \"gitlabTag\": \"v1.2.0\", \"environments\": [{\"environmentId\": \"$ENV\", \"scheduledDate\": \"2026-10-20\"}], \"issueIds\": [\"$ID\"] }" | jq -r .id) RE=$(curl -s -H "$H" $API/releases/$REL | jq -r '.environments[0].id') curl -s -X POST -H "$H" -H 'Content-Type: application/json' $API/release-environments/$RE/approve -d '{}' curl -s -X PATCH -H "$H" -H 'Content-Type: application/json' $API/release-environments/$RE -d '{"status":"released","actualDate":"2026-10-20"}' ``` 上版檢查清單:`GET /releases/:id/checklist`,勾選是 `PATCH /release-checklist-items/:id` 帶 `{"status":"done"}`(另有 `pending`/`not_applicable`)。 **記錄寄出文件與簽回** ```bash curl -s -X POST -H "$H" -H 'Content-Type: application/json' $API/folders/$FOLDER/documents \ -d '{"kind":"spec","title":"會員系統需求規格文件","version":"v2","sentAt":"2026-10-01"}' curl -s -X PUT -H "$H" -H 'Content-Type: application/json' $API/project-documents/$DOC -d '{"signedAt":"2026-10-05"}' ``` **找人、看自己** `GET /auth/me`、`GET /users/search-list?q=名字`(模糊)、`GET /users/search?email=…`(精確,查不到 404)、 `GET /folders/:id/members`(某客戶的成員,用來知道該找誰)。 ## 使用守則 - **後端不會拒絕認不得的欄位,只會安靜地忽略。** 欄位名稱對照 , 不要憑印象猜(例如指派是 `executorAssigneeIds`,沒有 `assigneeId` 這個寫入欄位)。 - **開議題前先查重**:用 `keyword` 搜一次還開著的議題,已經有就回報那一張,不要再開。 - **刪除不可逆**:`DELETE /issues/:id` 會連同留言與測試案例一起刪,動手前先問人。 - **改成 `closed` 前先確認**對方要的是「不用做了」而不是「做完了」(那是 `resolved`)。 - **上版相關的寫入先跟人確認**:它是對外的紀錄,稽核時要說得出是誰同意的。 - 回報時帶議題編號(`E1-16`),並附網頁連結 `https://mataan.anyong.com.tw/issues/E1-16`,讓人點得進去。 - 憑證只拿得到名稱與連結;密碼請使用者自己到網頁上看,不要嘗試繞過。