[{"data":1,"prerenderedAt":5501},["ShallowReactive",2],{"blog:\u002Fblog\u002Fnuxt\u002Fnitro-api-design":3},{"id":4,"title":5,"author":6,"body":7,"category":5485,"date":5486,"description":5487,"draft":5488,"extension":5489,"image":5490,"meta":5491,"navigation":384,"path":5492,"seo":5493,"series":5494,"seriesOrder":428,"seriesTitle":5495,"stem":5496,"tags":5497,"updatedAt":5490,"__hash__":5500},"blog\u002Fblog\u002Fnuxt\u002Fnitro-api-design\u002Findex.md","Nitro Server API 設計模式","charles",{"type":8,"value":9,"toc":5437},"minimark",[10,14,18,37,40,44,49,60,63,157,168,177,210,212,216,220,223,841,843,847,851,1358,1362,1469,1472,1482,1555,1557,1560,1564,1701,1704,1770,1772,1775,1779,2010,2013,2115,2118,2197,2199,2202,2205,2266,2270,2591,2593,2596,2599,2714,2717,2802,2805,2963,2965,2969,2973,3738,3742,4355,4359,5030,5032,5035,5038,5044,5055,5061,5065,5073,5083,5149,5152,5157,5165,5242,5244,5247,5250,5332,5334,5337,5396,5398,5401,5433],[11,12,13],"h2",{"id":13},"這篇要解決什麼問題",[15,16,17],"p",{},"Server API 是連接前端與資料庫的橋樑。設計良好的 API 需要考慮：",[19,20,21,25,28,31,34],"ul",{},[22,23,24],"li",{},"RESTful API 的目錄結構設計",[22,26,27],{},"Zod 請求驗證整合",[22,29,30],{},"統一回應格式（列表分頁、單筆資料、錯誤）",[22,32,33],{},"權限檢查與 createError 錯誤處理",[22,35,36],{},"操作日誌記錄",[38,39],"hr",{},[11,41,43],{"id":42},"restful-目錄結構","RESTful 目錄結構",[45,46,48],"h3",{"id":47},"版本化-api","版本化 API",[50,51,56],"pre",{"className":52,"code":54,"language":55},[53],"language-text","server\u002F\n├── api\u002F\n│   ├── v1\u002F                       # 版本化業務 API\n│   │   ├── resources\u002F\n│   │   │   ├── index.get.ts      # GET \u002Fapi\u002Fv1\u002Fresources（列表）\n│   │   │   ├── index.post.ts     # POST \u002Fapi\u002Fv1\u002Fresources（新增）\n│   │   │   └── [id]\u002F\n│   │   │       ├── index.get.ts      # GET \u002Fapi\u002Fv1\u002Fresources\u002F:id\n│   │   │       ├── index.patch.ts    # PATCH \u002Fapi\u002Fv1\u002Fresources\u002F:id\n│   │   │       └── index.delete.ts   # DELETE \u002Fapi\u002Fv1\u002Fresources\u002F:id\n│   ├── auth\u002F                     # 認證 API\n│   └── admin\u002F                    # 管理員 API\n├── middleware\u002F                   # Server middleware\n├── routes\u002Fauth\u002F                  # OAuth routes\n└── utils\u002F                        # 共用工具函式\n","text",[57,58,54],"code",{"__ignoreMap":59},"",[45,61,62],{"id":62},"路由命名規則",[64,65,66,82],"table",{},[67,68,69],"thead",{},[70,71,72,76,79],"tr",{},[73,74,75],"th",{},"檔案名稱",[73,77,78],{},"HTTP 方法",[73,80,81],{},"路徑",[83,84,85,101,115,129,143],"tbody",{},[70,86,87,93,96],{},[88,89,90],"td",{},[57,91,92],{},"index.get.ts",[88,94,95],{},"GET",[88,97,98],{},[57,99,100],{},"\u002Fapi\u002Fv1\u002Fusers",[70,102,103,108,111],{},[88,104,105],{},[57,106,107],{},"index.post.ts",[88,109,110],{},"POST",[88,112,113],{},[57,114,100],{},[70,116,117,122,124],{},[88,118,119],{},[57,120,121],{},"[id]\u002Findex.get.ts",[88,123,95],{},[88,125,126],{},[57,127,128],{},"\u002Fapi\u002Fv1\u002Fusers\u002F:id",[70,130,131,136,139],{},[88,132,133],{},[57,134,135],{},"[id]\u002Findex.patch.ts",[88,137,138],{},"PATCH",[88,140,141],{},[57,142,128],{},[70,144,145,150,153],{},[88,146,147],{},[57,148,149],{},"[id]\u002Findex.delete.ts",[88,151,152],{},"DELETE",[88,154,155],{},[57,156,128],{},[45,158,160,161,164,165],{"id":159},"為什麼使用-idindexts-而非-idts","為什麼使用 ",[57,162,163],{},"[id]\u002Findex.*.ts"," 而非 ",[57,166,167],{},"[id].*.ts",[15,169,170,171,173,174,176],{},"使用目錄結構 ",[57,172,163],{}," 而非扁平結構 ",[57,175,167],{},"，可以：",[178,179,180,194,204],"ol",{},[22,181,182,186,187,190,191],{},[183,184,185],"strong",{},"避免路由衝突","：同一目錄不會同時有 ",[57,188,189],{},"[id].ts"," 和 ",[57,192,193],{},"[id]\u002Fxxx.ts",[22,195,196,199,200,203],{},[183,197,198],{},"方便擴展","：未來可在 ",[57,201,202],{},"[id]\u002F"," 目錄下新增子路由",[22,205,206,209],{},[183,207,208],{},"結構清晰","：每個資源的 CRUD 操作集中管理",[38,211],{},[11,213,215],{"id":214},"api-開發流程","API 開發流程",[45,217,219],{"id":218},"標準流程7-步驟","標準流程（7 步驟）",[15,221,222],{},"每個 API 開發遵循以下順序：",[50,224,228],{"className":225,"code":226,"language":227,"meta":59,"style":59},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark","export default defineEventHandler(async (event) => {\n  \u002F\u002F 1. 權限檢查（最先執行）\n  const { user } = await requireUserSession(event, {\n    user: { role: [\"admin\", \"manager\", \"staff\"] },\n  });\n\n  \u002F\u002F 2. 驗證請求資料（Query 或 Body）\n  const body = await readValidatedBody(event, createResourceSchema.parse);\n\n  \u002F\u002F 3. 取得 Supabase Client\n  const supabase = await getSupabaseWithContext(event);\n  const db = supabase.schema(\"your_schema\");\n\n  \u002F\u002F 4. 執行業務邏輯\n  const { data, error } = await db\n    .from(\"resources\")\n    .insert(body)\n    .select()\n    .single();\n\n  \u002F\u002F 5. 錯誤處理\n  if (error) {\n    throw createError({ statusCode: 500, message: \"操作失敗\" });\n  }\n\n  \u002F\u002F 6. 記錄操作日誌（寫入操作）\n  await db.from(\"operation_logs\").insert({\n    user_id: user.id,\n    action: \"create\",\n    target_type: \"resource\",\n    target_id: data.id.toString(),\n  });\n\n  \u002F\u002F 7. 回傳統一格式\n  setResponseStatus(event, 201);\n  return { data };\n});\n","typescript",[57,229,230,271,278,314,368,379,386,392,426,431,437,460,490,495,501,525,546,561,572,585,590,596,613,656,662,667,673,704,722,739,756,779,788,793,799,818,831],{"__ignoreMap":59},[231,232,235,239,242,246,250,254,258,262,265,268],"span",{"class":233,"line":234},"line",1,[231,236,238],{"class":237},"sVHd0","export",[231,240,241],{"class":237}," default",[231,243,245],{"class":244},"sGLFI"," defineEventHandler",[231,247,249],{"class":248},"su5hD","(",[231,251,253],{"class":252},"sbsja","async",[231,255,257],{"class":256},"sP7_E"," (",[231,259,261],{"class":260},"s99_P","event",[231,263,264],{"class":256},")",[231,266,267],{"class":252}," =>",[231,269,270],{"class":256}," {\n",[231,272,274],{"class":233,"line":273},2,[231,275,277],{"class":276},"sutJx","  \u002F\u002F 1. 權限檢查（最先執行）\n",[231,279,281,284,287,291,294,298,301,304,307,309,312],{"class":233,"line":280},3,[231,282,283],{"class":252},"  const",[231,285,286],{"class":256}," {",[231,288,290],{"class":289},"s_hVV"," user",[231,292,293],{"class":256}," }",[231,295,297],{"class":296},"smGrS"," =",[231,299,300],{"class":237}," await",[231,302,303],{"class":244}," requireUserSession",[231,305,249],{"class":306},"skxfh",[231,308,261],{"class":248},[231,310,311],{"class":256},",",[231,313,270],{"class":256},[231,315,317,320,323,325,328,330,333,337,341,343,345,348,351,353,355,357,360,362,365],{"class":233,"line":316},4,[231,318,319],{"class":306},"    user",[231,321,322],{"class":256},":",[231,324,286],{"class":256},[231,326,327],{"class":306}," role",[231,329,322],{"class":256},[231,331,332],{"class":306}," [",[231,334,336],{"class":335},"sjJ54","\"",[231,338,340],{"class":339},"s_sjI","admin",[231,342,336],{"class":335},[231,344,311],{"class":256},[231,346,347],{"class":335}," \"",[231,349,350],{"class":339},"manager",[231,352,336],{"class":335},[231,354,311],{"class":256},[231,356,347],{"class":335},[231,358,359],{"class":339},"staff",[231,361,336],{"class":335},[231,363,364],{"class":306},"] ",[231,366,367],{"class":256},"},\n",[231,369,371,374,376],{"class":233,"line":370},5,[231,372,373],{"class":256},"  }",[231,375,264],{"class":306},[231,377,378],{"class":256},";\n",[231,380,382],{"class":233,"line":381},6,[231,383,385],{"emptyLinePlaceholder":384},true,"\n",[231,387,389],{"class":233,"line":388},7,[231,390,391],{"class":276},"  \u002F\u002F 2. 驗證請求資料（Query 或 Body）\n",[231,393,395,397,400,402,404,407,409,411,413,416,419,422,424],{"class":233,"line":394},8,[231,396,283],{"class":252},[231,398,399],{"class":289}," body",[231,401,297],{"class":296},[231,403,300],{"class":237},[231,405,406],{"class":244}," readValidatedBody",[231,408,249],{"class":306},[231,410,261],{"class":248},[231,412,311],{"class":256},[231,414,415],{"class":248}," createResourceSchema",[231,417,418],{"class":256},".",[231,420,421],{"class":248},"parse",[231,423,264],{"class":306},[231,425,378],{"class":256},[231,427,429],{"class":233,"line":428},9,[231,430,385],{"emptyLinePlaceholder":384},[231,432,434],{"class":233,"line":433},10,[231,435,436],{"class":276},"  \u002F\u002F 3. 取得 Supabase Client\n",[231,438,440,442,445,447,449,452,454,456,458],{"class":233,"line":439},11,[231,441,283],{"class":252},[231,443,444],{"class":289}," supabase",[231,446,297],{"class":296},[231,448,300],{"class":237},[231,450,451],{"class":244}," getSupabaseWithContext",[231,453,249],{"class":306},[231,455,261],{"class":248},[231,457,264],{"class":306},[231,459,378],{"class":256},[231,461,463,465,468,470,472,474,477,479,481,484,486,488],{"class":233,"line":462},12,[231,464,283],{"class":252},[231,466,467],{"class":289}," db",[231,469,297],{"class":296},[231,471,444],{"class":248},[231,473,418],{"class":256},[231,475,476],{"class":244},"schema",[231,478,249],{"class":306},[231,480,336],{"class":335},[231,482,483],{"class":339},"your_schema",[231,485,336],{"class":335},[231,487,264],{"class":306},[231,489,378],{"class":256},[231,491,493],{"class":233,"line":492},13,[231,494,385],{"emptyLinePlaceholder":384},[231,496,498],{"class":233,"line":497},14,[231,499,500],{"class":276},"  \u002F\u002F 4. 執行業務邏輯\n",[231,502,504,506,508,511,513,516,518,520,522],{"class":233,"line":503},15,[231,505,283],{"class":252},[231,507,286],{"class":256},[231,509,510],{"class":289}," data",[231,512,311],{"class":256},[231,514,515],{"class":289}," error",[231,517,293],{"class":256},[231,519,297],{"class":296},[231,521,300],{"class":237},[231,523,524],{"class":248}," db\n",[231,526,528,531,534,536,538,541,543],{"class":233,"line":527},16,[231,529,530],{"class":256},"    .",[231,532,533],{"class":244},"from",[231,535,249],{"class":306},[231,537,336],{"class":335},[231,539,540],{"class":339},"resources",[231,542,336],{"class":335},[231,544,545],{"class":306},")\n",[231,547,549,551,554,556,559],{"class":233,"line":548},17,[231,550,530],{"class":256},[231,552,553],{"class":244},"insert",[231,555,249],{"class":306},[231,557,558],{"class":248},"body",[231,560,545],{"class":306},[231,562,564,566,569],{"class":233,"line":563},18,[231,565,530],{"class":256},[231,567,568],{"class":244},"select",[231,570,571],{"class":306},"()\n",[231,573,575,577,580,583],{"class":233,"line":574},19,[231,576,530],{"class":256},[231,578,579],{"class":244},"single",[231,581,582],{"class":306},"()",[231,584,378],{"class":256},[231,586,588],{"class":233,"line":587},20,[231,589,385],{"emptyLinePlaceholder":384},[231,591,593],{"class":233,"line":592},21,[231,594,595],{"class":276},"  \u002F\u002F 5. 錯誤處理\n",[231,597,599,602,604,607,610],{"class":233,"line":598},22,[231,600,601],{"class":237},"  if",[231,603,257],{"class":306},[231,605,606],{"class":248},"error",[231,608,609],{"class":306},") ",[231,611,612],{"class":256},"{\n",[231,614,616,619,622,624,627,630,632,636,638,641,643,645,648,650,652,654],{"class":233,"line":615},23,[231,617,618],{"class":237},"    throw",[231,620,621],{"class":244}," createError",[231,623,249],{"class":306},[231,625,626],{"class":256},"{",[231,628,629],{"class":306}," statusCode",[231,631,322],{"class":256},[231,633,635],{"class":634},"srdBf"," 500",[231,637,311],{"class":256},[231,639,640],{"class":306}," message",[231,642,322],{"class":256},[231,644,347],{"class":335},[231,646,647],{"class":339},"操作失敗",[231,649,336],{"class":335},[231,651,293],{"class":256},[231,653,264],{"class":306},[231,655,378],{"class":256},[231,657,659],{"class":233,"line":658},24,[231,660,661],{"class":256},"  }\n",[231,663,665],{"class":233,"line":664},25,[231,666,385],{"emptyLinePlaceholder":384},[231,668,670],{"class":233,"line":669},26,[231,671,672],{"class":276},"  \u002F\u002F 6. 記錄操作日誌（寫入操作）\n",[231,674,676,679,681,683,685,687,689,692,694,696,698,700,702],{"class":233,"line":675},27,[231,677,678],{"class":237},"  await",[231,680,467],{"class":248},[231,682,418],{"class":256},[231,684,533],{"class":244},[231,686,249],{"class":306},[231,688,336],{"class":335},[231,690,691],{"class":339},"operation_logs",[231,693,336],{"class":335},[231,695,264],{"class":306},[231,697,418],{"class":256},[231,699,553],{"class":244},[231,701,249],{"class":306},[231,703,612],{"class":256},[231,705,707,710,712,714,716,719],{"class":233,"line":706},28,[231,708,709],{"class":306},"    user_id",[231,711,322],{"class":256},[231,713,290],{"class":248},[231,715,418],{"class":256},[231,717,718],{"class":248},"id",[231,720,721],{"class":256},",\n",[231,723,725,728,730,732,735,737],{"class":233,"line":724},29,[231,726,727],{"class":306},"    action",[231,729,322],{"class":256},[231,731,347],{"class":335},[231,733,734],{"class":339},"create",[231,736,336],{"class":335},[231,738,721],{"class":256},[231,740,742,745,747,749,752,754],{"class":233,"line":741},30,[231,743,744],{"class":306},"    target_type",[231,746,322],{"class":256},[231,748,347],{"class":335},[231,750,751],{"class":339},"resource",[231,753,336],{"class":335},[231,755,721],{"class":256},[231,757,759,762,764,766,768,770,772,775,777],{"class":233,"line":758},31,[231,760,761],{"class":306},"    target_id",[231,763,322],{"class":256},[231,765,510],{"class":248},[231,767,418],{"class":256},[231,769,718],{"class":248},[231,771,418],{"class":256},[231,773,774],{"class":244},"toString",[231,776,582],{"class":306},[231,778,721],{"class":256},[231,780,782,784,786],{"class":233,"line":781},32,[231,783,373],{"class":256},[231,785,264],{"class":306},[231,787,378],{"class":256},[231,789,791],{"class":233,"line":790},33,[231,792,385],{"emptyLinePlaceholder":384},[231,794,796],{"class":233,"line":795},34,[231,797,798],{"class":276},"  \u002F\u002F 7. 回傳統一格式\n",[231,800,802,805,807,809,811,814,816],{"class":233,"line":801},35,[231,803,804],{"class":244},"  setResponseStatus",[231,806,249],{"class":306},[231,808,261],{"class":248},[231,810,311],{"class":256},[231,812,813],{"class":634}," 201",[231,815,264],{"class":306},[231,817,378],{"class":256},[231,819,821,824,826,828],{"class":233,"line":820},36,[231,822,823],{"class":237},"  return",[231,825,286],{"class":256},[231,827,510],{"class":248},[231,829,830],{"class":256}," };\n",[231,832,834,837,839],{"class":233,"line":833},37,[231,835,836],{"class":256},"}",[231,838,264],{"class":248},[231,840,378],{"class":256},[38,842],{},[11,844,846],{"id":845},"zod-請求驗證","Zod 請求驗證",[45,848,850],{"id":849},"schema-定義放在-sharedtypes","Schema 定義（放在 shared\u002Ftypes\u002F）",[50,852,854],{"className":225,"code":853,"language":227,"meta":59,"style":59},"\u002F\u002F shared\u002Ftypes\u002Fresources.ts\nimport { z } from \"zod\";\n\n\u002F\u002F 共用分頁查詢 Schema（可複用）\nexport const paginationQuerySchema = z.object({\n  page: z.coerce.number().int().positive().default(1),\n  pageSize: z.coerce.number().int().positive().max(1000).default(10),\n  search: z.string().optional(),\n  sortBy: z.string().optional(),\n  sortDir: z.enum([\"asc\", \"desc\"]).default(\"desc\"),\n});\n\n\u002F\u002F 新增資源 Schema\nexport const createResourceSchema = z.object({\n  name: z.string().min(1, \"名稱必填\").max(200),\n  description: z.string().max(500).nullable().optional(),\n});\n\n\u002F\u002F 更新資源 Schema（所有欄位變成可選）\nexport const updateResourceSchema = createResourceSchema.partial();\n\n\u002F\u002F 路徑參數 Schema\nexport const idParamSchema = z.object({\n  id: z.coerce.number().int().positive(),\n});\n",[57,855,856,861,885,889,894,917,966,1022,1047,1070,1122,1130,1134,1139,1159,1207,1248,1256,1260,1265,1287,1291,1296,1317,1350],{"__ignoreMap":59},[231,857,858],{"class":233,"line":234},[231,859,860],{"class":276},"\u002F\u002F shared\u002Ftypes\u002Fresources.ts\n",[231,862,863,866,868,871,873,876,878,881,883],{"class":233,"line":273},[231,864,865],{"class":237},"import",[231,867,286],{"class":256},[231,869,870],{"class":248}," z",[231,872,293],{"class":256},[231,874,875],{"class":237}," from",[231,877,347],{"class":335},[231,879,880],{"class":339},"zod",[231,882,336],{"class":335},[231,884,378],{"class":256},[231,886,887],{"class":233,"line":280},[231,888,385],{"emptyLinePlaceholder":384},[231,890,891],{"class":233,"line":316},[231,892,893],{"class":276},"\u002F\u002F 共用分頁查詢 Schema（可複用）\n",[231,895,896,898,901,904,906,908,910,913,915],{"class":233,"line":370},[231,897,238],{"class":237},[231,899,900],{"class":252}," const",[231,902,903],{"class":289}," paginationQuerySchema",[231,905,297],{"class":296},[231,907,870],{"class":248},[231,909,418],{"class":256},[231,911,912],{"class":244},"object",[231,914,249],{"class":248},[231,916,612],{"class":256},[231,918,919,922,924,926,928,931,933,936,938,940,943,945,947,950,952,954,957,959,962,964],{"class":233,"line":381},[231,920,921],{"class":306},"  page",[231,923,322],{"class":256},[231,925,870],{"class":248},[231,927,418],{"class":256},[231,929,930],{"class":248},"coerce",[231,932,418],{"class":256},[231,934,935],{"class":244},"number",[231,937,582],{"class":248},[231,939,418],{"class":256},[231,941,942],{"class":244},"int",[231,944,582],{"class":248},[231,946,418],{"class":256},[231,948,949],{"class":244},"positive",[231,951,582],{"class":248},[231,953,418],{"class":256},[231,955,956],{"class":244},"default",[231,958,249],{"class":248},[231,960,961],{"class":634},"1",[231,963,264],{"class":248},[231,965,721],{"class":256},[231,967,968,971,973,975,977,979,981,983,985,987,989,991,993,995,997,999,1002,1004,1007,1009,1011,1013,1015,1018,1020],{"class":233,"line":388},[231,969,970],{"class":306},"  pageSize",[231,972,322],{"class":256},[231,974,870],{"class":248},[231,976,418],{"class":256},[231,978,930],{"class":248},[231,980,418],{"class":256},[231,982,935],{"class":244},[231,984,582],{"class":248},[231,986,418],{"class":256},[231,988,942],{"class":244},[231,990,582],{"class":248},[231,992,418],{"class":256},[231,994,949],{"class":244},[231,996,582],{"class":248},[231,998,418],{"class":256},[231,1000,1001],{"class":244},"max",[231,1003,249],{"class":248},[231,1005,1006],{"class":634},"1000",[231,1008,264],{"class":248},[231,1010,418],{"class":256},[231,1012,956],{"class":244},[231,1014,249],{"class":248},[231,1016,1017],{"class":634},"10",[231,1019,264],{"class":248},[231,1021,721],{"class":256},[231,1023,1024,1027,1029,1031,1033,1036,1038,1040,1043,1045],{"class":233,"line":394},[231,1025,1026],{"class":306},"  search",[231,1028,322],{"class":256},[231,1030,870],{"class":248},[231,1032,418],{"class":256},[231,1034,1035],{"class":244},"string",[231,1037,582],{"class":248},[231,1039,418],{"class":256},[231,1041,1042],{"class":244},"optional",[231,1044,582],{"class":248},[231,1046,721],{"class":256},[231,1048,1049,1052,1054,1056,1058,1060,1062,1064,1066,1068],{"class":233,"line":428},[231,1050,1051],{"class":306},"  sortBy",[231,1053,322],{"class":256},[231,1055,870],{"class":248},[231,1057,418],{"class":256},[231,1059,1035],{"class":244},[231,1061,582],{"class":248},[231,1063,418],{"class":256},[231,1065,1042],{"class":244},[231,1067,582],{"class":248},[231,1069,721],{"class":256},[231,1071,1072,1075,1077,1079,1081,1084,1087,1089,1092,1094,1096,1098,1101,1103,1106,1108,1110,1112,1114,1116,1118,1120],{"class":233,"line":433},[231,1073,1074],{"class":306},"  sortDir",[231,1076,322],{"class":256},[231,1078,870],{"class":248},[231,1080,418],{"class":256},[231,1082,1083],{"class":244},"enum",[231,1085,1086],{"class":248},"([",[231,1088,336],{"class":335},[231,1090,1091],{"class":339},"asc",[231,1093,336],{"class":335},[231,1095,311],{"class":256},[231,1097,347],{"class":335},[231,1099,1100],{"class":339},"desc",[231,1102,336],{"class":335},[231,1104,1105],{"class":248},"])",[231,1107,418],{"class":256},[231,1109,956],{"class":244},[231,1111,249],{"class":248},[231,1113,336],{"class":335},[231,1115,1100],{"class":339},[231,1117,336],{"class":335},[231,1119,264],{"class":248},[231,1121,721],{"class":256},[231,1123,1124,1126,1128],{"class":233,"line":439},[231,1125,836],{"class":256},[231,1127,264],{"class":248},[231,1129,378],{"class":256},[231,1131,1132],{"class":233,"line":462},[231,1133,385],{"emptyLinePlaceholder":384},[231,1135,1136],{"class":233,"line":492},[231,1137,1138],{"class":276},"\u002F\u002F 新增資源 Schema\n",[231,1140,1141,1143,1145,1147,1149,1151,1153,1155,1157],{"class":233,"line":497},[231,1142,238],{"class":237},[231,1144,900],{"class":252},[231,1146,415],{"class":289},[231,1148,297],{"class":296},[231,1150,870],{"class":248},[231,1152,418],{"class":256},[231,1154,912],{"class":244},[231,1156,249],{"class":248},[231,1158,612],{"class":256},[231,1160,1161,1164,1166,1168,1170,1172,1174,1176,1179,1181,1183,1185,1187,1190,1192,1194,1196,1198,1200,1203,1205],{"class":233,"line":503},[231,1162,1163],{"class":306},"  name",[231,1165,322],{"class":256},[231,1167,870],{"class":248},[231,1169,418],{"class":256},[231,1171,1035],{"class":244},[231,1173,582],{"class":248},[231,1175,418],{"class":256},[231,1177,1178],{"class":244},"min",[231,1180,249],{"class":248},[231,1182,961],{"class":634},[231,1184,311],{"class":256},[231,1186,347],{"class":335},[231,1188,1189],{"class":339},"名稱必填",[231,1191,336],{"class":335},[231,1193,264],{"class":248},[231,1195,418],{"class":256},[231,1197,1001],{"class":244},[231,1199,249],{"class":248},[231,1201,1202],{"class":634},"200",[231,1204,264],{"class":248},[231,1206,721],{"class":256},[231,1208,1209,1212,1214,1216,1218,1220,1222,1224,1226,1228,1231,1233,1235,1238,1240,1242,1244,1246],{"class":233,"line":527},[231,1210,1211],{"class":306},"  description",[231,1213,322],{"class":256},[231,1215,870],{"class":248},[231,1217,418],{"class":256},[231,1219,1035],{"class":244},[231,1221,582],{"class":248},[231,1223,418],{"class":256},[231,1225,1001],{"class":244},[231,1227,249],{"class":248},[231,1229,1230],{"class":634},"500",[231,1232,264],{"class":248},[231,1234,418],{"class":256},[231,1236,1237],{"class":244},"nullable",[231,1239,582],{"class":248},[231,1241,418],{"class":256},[231,1243,1042],{"class":244},[231,1245,582],{"class":248},[231,1247,721],{"class":256},[231,1249,1250,1252,1254],{"class":233,"line":548},[231,1251,836],{"class":256},[231,1253,264],{"class":248},[231,1255,378],{"class":256},[231,1257,1258],{"class":233,"line":563},[231,1259,385],{"emptyLinePlaceholder":384},[231,1261,1262],{"class":233,"line":574},[231,1263,1264],{"class":276},"\u002F\u002F 更新資源 Schema（所有欄位變成可選）\n",[231,1266,1267,1269,1271,1274,1276,1278,1280,1283,1285],{"class":233,"line":587},[231,1268,238],{"class":237},[231,1270,900],{"class":252},[231,1272,1273],{"class":289}," updateResourceSchema",[231,1275,297],{"class":296},[231,1277,415],{"class":248},[231,1279,418],{"class":256},[231,1281,1282],{"class":244},"partial",[231,1284,582],{"class":248},[231,1286,378],{"class":256},[231,1288,1289],{"class":233,"line":592},[231,1290,385],{"emptyLinePlaceholder":384},[231,1292,1293],{"class":233,"line":598},[231,1294,1295],{"class":276},"\u002F\u002F 路徑參數 Schema\n",[231,1297,1298,1300,1302,1305,1307,1309,1311,1313,1315],{"class":233,"line":615},[231,1299,238],{"class":237},[231,1301,900],{"class":252},[231,1303,1304],{"class":289}," idParamSchema",[231,1306,297],{"class":296},[231,1308,870],{"class":248},[231,1310,418],{"class":256},[231,1312,912],{"class":244},[231,1314,249],{"class":248},[231,1316,612],{"class":256},[231,1318,1319,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348],{"class":233,"line":658},[231,1320,1321],{"class":306},"  id",[231,1323,322],{"class":256},[231,1325,870],{"class":248},[231,1327,418],{"class":256},[231,1329,930],{"class":248},[231,1331,418],{"class":256},[231,1333,935],{"class":244},[231,1335,582],{"class":248},[231,1337,418],{"class":256},[231,1339,942],{"class":244},[231,1341,582],{"class":248},[231,1343,418],{"class":256},[231,1345,949],{"class":244},[231,1347,582],{"class":248},[231,1349,721],{"class":256},[231,1351,1352,1354,1356],{"class":233,"line":664},[231,1353,836],{"class":256},[231,1355,264],{"class":248},[231,1357,378],{"class":256},[45,1359,1361],{"id":1360},"在-api-中使用驗證","在 API 中使用驗證",[50,1363,1365],{"className":225,"code":1364,"language":227,"meta":59,"style":59},"\u002F\u002F GET 請求：驗證 Query Parameters\nconst query = await getValidatedQuery(event, paginationQuerySchema.parse);\n\n\u002F\u002F POST\u002FPATCH 請求：驗證 Request Body\nconst body = await readValidatedBody(event, createResourceSchema.parse);\n\n\u002F\u002F 路徑參數驗證\nconst params = await getValidatedRouterParams(event, idParamSchema.parse);\n",[57,1366,1367,1372,1401,1405,1410,1434,1438,1443],{"__ignoreMap":59},[231,1368,1369],{"class":233,"line":234},[231,1370,1371],{"class":276},"\u002F\u002F GET 請求：驗證 Query Parameters\n",[231,1373,1374,1377,1380,1382,1384,1387,1390,1392,1394,1396,1399],{"class":233,"line":273},[231,1375,1376],{"class":252},"const",[231,1378,1379],{"class":289}," query",[231,1381,297],{"class":296},[231,1383,300],{"class":237},[231,1385,1386],{"class":244}," getValidatedQuery",[231,1388,1389],{"class":248},"(event",[231,1391,311],{"class":256},[231,1393,903],{"class":248},[231,1395,418],{"class":256},[231,1397,1398],{"class":248},"parse)",[231,1400,378],{"class":256},[231,1402,1403],{"class":233,"line":280},[231,1404,385],{"emptyLinePlaceholder":384},[231,1406,1407],{"class":233,"line":316},[231,1408,1409],{"class":276},"\u002F\u002F POST\u002FPATCH 請求：驗證 Request Body\n",[231,1411,1412,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432],{"class":233,"line":370},[231,1413,1376],{"class":252},[231,1415,399],{"class":289},[231,1417,297],{"class":296},[231,1419,300],{"class":237},[231,1421,406],{"class":244},[231,1423,1389],{"class":248},[231,1425,311],{"class":256},[231,1427,415],{"class":248},[231,1429,418],{"class":256},[231,1431,1398],{"class":248},[231,1433,378],{"class":256},[231,1435,1436],{"class":233,"line":381},[231,1437,385],{"emptyLinePlaceholder":384},[231,1439,1440],{"class":233,"line":388},[231,1441,1442],{"class":276},"\u002F\u002F 路徑參數驗證\n",[231,1444,1445,1447,1450,1452,1454,1457,1459,1461,1463,1465,1467],{"class":233,"line":394},[231,1446,1376],{"class":252},[231,1448,1449],{"class":289}," params",[231,1451,297],{"class":296},[231,1453,300],{"class":237},[231,1455,1456],{"class":244}," getValidatedRouterParams",[231,1458,1389],{"class":248},[231,1460,311],{"class":256},[231,1462,1304],{"class":248},[231,1464,418],{"class":256},[231,1466,1398],{"class":248},[231,1468,378],{"class":256},[45,1470,1471],{"id":1471},"驗證失敗自動回應",[15,1473,1474,1475,190,1478,1481],{},"使用 ",[57,1476,1477],{},"getValidatedQuery",[57,1479,1480],{},"readValidatedBody"," 時，驗證失敗會自動拋出 400 錯誤：",[50,1483,1487],{"className":1484,"code":1485,"language":1486,"meta":59,"style":59},"language-json shiki shiki-themes material-theme-lighter github-light github-dark","{\n  \"statusCode\": 400,\n  \"statusMessage\": \"Bad Request\",\n  \"message\": \"名稱必填\"\n}\n","json",[57,1488,1489,1493,1512,1532,1550],{"__ignoreMap":59},[231,1490,1491],{"class":233,"line":234},[231,1492,612],{"class":256},[231,1494,1495,1499,1503,1505,1507,1510],{"class":233,"line":273},[231,1496,1498],{"class":1497},"s39Yj","  \"",[231,1500,1502],{"class":1501},"sseR_","statusCode",[231,1504,336],{"class":1497},[231,1506,322],{"class":256},[231,1508,1509],{"class":634}," 400",[231,1511,721],{"class":256},[231,1513,1514,1516,1519,1521,1523,1525,1528,1530],{"class":233,"line":280},[231,1515,1498],{"class":1497},[231,1517,1518],{"class":1501},"statusMessage",[231,1520,336],{"class":1497},[231,1522,322],{"class":256},[231,1524,347],{"class":335},[231,1526,1527],{"class":339},"Bad Request",[231,1529,336],{"class":335},[231,1531,721],{"class":256},[231,1533,1534,1536,1539,1541,1543,1545,1547],{"class":233,"line":316},[231,1535,1498],{"class":1497},[231,1537,1538],{"class":1501},"message",[231,1540,336],{"class":1497},[231,1542,322],{"class":256},[231,1544,347],{"class":335},[231,1546,1189],{"class":339},[231,1548,1549],{"class":335},"\"\n",[231,1551,1552],{"class":233,"line":370},[231,1553,1554],{"class":256},"}\n",[38,1556],{},[11,1558,1559],{"id":1559},"權限檢查",[45,1561,1563],{"id":1562},"requireusersession-用法","requireUserSession 用法",[50,1565,1567],{"className":225,"code":1566,"language":227,"meta":59,"style":59},"\u002F\u002F 基本用法：僅檢查是否登入\nconst { user } = await requireUserSession(event);\n\n\u002F\u002F 指定允許的角色\nconst { user } = await requireUserSession(event, {\n  user: { role: [\"admin\", \"manager\"] },\n});\n\n\u002F\u002F 取得完整 session 資訊\nconst { user, session } = await requireUserSession(event);\n",[57,1568,1569,1574,1595,1599,1604,1626,1659,1667,1671,1676],{"__ignoreMap":59},[231,1570,1571],{"class":233,"line":234},[231,1572,1573],{"class":276},"\u002F\u002F 基本用法：僅檢查是否登入\n",[231,1575,1576,1578,1580,1582,1584,1586,1588,1590,1593],{"class":233,"line":273},[231,1577,1376],{"class":252},[231,1579,286],{"class":256},[231,1581,290],{"class":289},[231,1583,293],{"class":256},[231,1585,297],{"class":296},[231,1587,300],{"class":237},[231,1589,303],{"class":244},[231,1591,1592],{"class":248},"(event)",[231,1594,378],{"class":256},[231,1596,1597],{"class":233,"line":280},[231,1598,385],{"emptyLinePlaceholder":384},[231,1600,1601],{"class":233,"line":316},[231,1602,1603],{"class":276},"\u002F\u002F 指定允許的角色\n",[231,1605,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624],{"class":233,"line":370},[231,1607,1376],{"class":252},[231,1609,286],{"class":256},[231,1611,290],{"class":289},[231,1613,293],{"class":256},[231,1615,297],{"class":296},[231,1617,300],{"class":237},[231,1619,303],{"class":244},[231,1621,1389],{"class":248},[231,1623,311],{"class":256},[231,1625,270],{"class":256},[231,1627,1628,1631,1633,1635,1637,1639,1641,1643,1645,1647,1649,1651,1653,1655,1657],{"class":233,"line":381},[231,1629,1630],{"class":306},"  user",[231,1632,322],{"class":256},[231,1634,286],{"class":256},[231,1636,327],{"class":306},[231,1638,322],{"class":256},[231,1640,332],{"class":248},[231,1642,336],{"class":335},[231,1644,340],{"class":339},[231,1646,336],{"class":335},[231,1648,311],{"class":256},[231,1650,347],{"class":335},[231,1652,350],{"class":339},[231,1654,336],{"class":335},[231,1656,364],{"class":248},[231,1658,367],{"class":256},[231,1660,1661,1663,1665],{"class":233,"line":388},[231,1662,836],{"class":256},[231,1664,264],{"class":248},[231,1666,378],{"class":256},[231,1668,1669],{"class":233,"line":394},[231,1670,385],{"emptyLinePlaceholder":384},[231,1672,1673],{"class":233,"line":428},[231,1674,1675],{"class":276},"\u002F\u002F 取得完整 session 資訊\n",[231,1677,1678,1680,1682,1684,1686,1689,1691,1693,1695,1697,1699],{"class":233,"line":433},[231,1679,1376],{"class":252},[231,1681,286],{"class":256},[231,1683,290],{"class":289},[231,1685,311],{"class":256},[231,1687,1688],{"class":289}," session",[231,1690,293],{"class":256},[231,1692,297],{"class":296},[231,1694,300],{"class":237},[231,1696,303],{"class":244},[231,1698,1592],{"class":248},[231,1700,378],{"class":256},[45,1702,1703],{"id":1703},"角色檢查順序",[64,1705,1706,1719],{},[67,1707,1708],{},[70,1709,1710,1713,1716],{},[73,1711,1712],{},"角色",[73,1714,1715],{},"權限等級",[73,1717,1718],{},"可存取範圍",[83,1720,1721,1733,1745,1757],{},[70,1722,1723,1727,1730],{},[88,1724,1725],{},[57,1726,340],{},[88,1728,1729],{},"最高",[88,1731,1732],{},"完整系統管理",[70,1734,1735,1739,1742],{},[88,1736,1737],{},[57,1738,350],{},[88,1740,1741],{},"高",[88,1743,1744],{},"部門管理、資料 CRUD",[70,1746,1747,1751,1754],{},[88,1748,1749],{},[57,1750,359],{},[88,1752,1753],{},"中",[88,1755,1756],{},"基本資料讀取",[70,1758,1759,1764,1767],{},[88,1760,1761],{},[57,1762,1763],{},"unauthorized",[88,1765,1766],{},"最低",[88,1768,1769],{},"無權限（等待授權）",[38,1771],{},[11,1773,1774],{"id":1774},"統一回應格式",[45,1776,1778],{"id":1777},"列表回應含分頁","列表回應（含分頁）",[50,1780,1782],{"className":225,"code":1781,"language":227,"meta":59,"style":59},"interface ListResponse&lt;T&gt; {\n  data: T[]\n  pagination: {\n    page: number\n    pageSize: number\n    total: number\n    totalPages: number\n  }\n}\n\n\u002F\u002F 實作範例\nreturn {\n  data: resources,\n  pagination: {\n    page: query.page,\n    pageSize: query.pageSize,\n    total: count || 0,\n    totalPages: Math.ceil((count || 0) \u002F query.pageSize),\n  },\n}\n",[57,1783,1784,1815,1829,1838,1849,1858,1867,1876,1880,1884,1888,1893,1900,1911,1919,1934,1949,1966,2001,2006],{"__ignoreMap":59},[231,1785,1786,1789,1793,1796,1799,1802,1805,1807,1810,1813],{"class":233,"line":234},[231,1787,1788],{"class":252},"interface",[231,1790,1792],{"class":1791},"sbgvK"," ListResponse",[231,1794,1795],{"class":248},"&",[231,1797,1798],{"class":1791},"lt",[231,1800,1801],{"class":248},";",[231,1803,1804],{"class":1791},"T",[231,1806,1795],{"class":248},[231,1808,1809],{"class":1791},"gt",[231,1811,1812],{"class":248},"; ",[231,1814,612],{"class":256},[231,1816,1817,1821,1823,1826],{"class":233,"line":273},[231,1818,1820],{"class":1819},"sucvu","  data",[231,1822,322],{"class":296},[231,1824,1825],{"class":1791}," T",[231,1827,1828],{"class":248},"[]\n",[231,1830,1831,1834,1836],{"class":233,"line":280},[231,1832,1833],{"class":1819},"  pagination",[231,1835,322],{"class":296},[231,1837,270],{"class":256},[231,1839,1840,1843,1845],{"class":233,"line":316},[231,1841,1842],{"class":1819},"    page",[231,1844,322],{"class":296},[231,1846,1848],{"class":1847},"sZMiF"," number\n",[231,1850,1851,1854,1856],{"class":233,"line":370},[231,1852,1853],{"class":1819},"    pageSize",[231,1855,322],{"class":296},[231,1857,1848],{"class":1847},[231,1859,1860,1863,1865],{"class":233,"line":381},[231,1861,1862],{"class":1819},"    total",[231,1864,322],{"class":296},[231,1866,1848],{"class":1847},[231,1868,1869,1872,1874],{"class":233,"line":388},[231,1870,1871],{"class":1819},"    totalPages",[231,1873,322],{"class":296},[231,1875,1848],{"class":1847},[231,1877,1878],{"class":233,"line":394},[231,1879,661],{"class":256},[231,1881,1882],{"class":233,"line":428},[231,1883,1554],{"class":256},[231,1885,1886],{"class":233,"line":433},[231,1887,385],{"emptyLinePlaceholder":384},[231,1889,1890],{"class":233,"line":439},[231,1891,1892],{"class":276},"\u002F\u002F 實作範例\n",[231,1894,1895,1898],{"class":233,"line":462},[231,1896,1897],{"class":237},"return",[231,1899,270],{"class":256},[231,1901,1902,1904,1906,1909],{"class":233,"line":492},[231,1903,1820],{"class":306},[231,1905,322],{"class":256},[231,1907,1908],{"class":248}," resources",[231,1910,721],{"class":256},[231,1912,1913,1915,1917],{"class":233,"line":497},[231,1914,1833],{"class":306},[231,1916,322],{"class":256},[231,1918,270],{"class":256},[231,1920,1921,1923,1925,1927,1929,1932],{"class":233,"line":503},[231,1922,1842],{"class":306},[231,1924,322],{"class":256},[231,1926,1379],{"class":248},[231,1928,418],{"class":256},[231,1930,1931],{"class":248},"page",[231,1933,721],{"class":256},[231,1935,1936,1938,1940,1942,1944,1947],{"class":233,"line":527},[231,1937,1853],{"class":306},[231,1939,322],{"class":256},[231,1941,1379],{"class":248},[231,1943,418],{"class":256},[231,1945,1946],{"class":248},"pageSize",[231,1948,721],{"class":256},[231,1950,1951,1953,1955,1958,1961,1964],{"class":233,"line":548},[231,1952,1862],{"class":306},[231,1954,322],{"class":256},[231,1956,1957],{"class":248}," count ",[231,1959,1960],{"class":296},"||",[231,1962,1963],{"class":634}," 0",[231,1965,721],{"class":256},[231,1967,1968,1970,1972,1975,1977,1980,1983,1985,1987,1989,1992,1994,1996,1999],{"class":233,"line":563},[231,1969,1871],{"class":306},[231,1971,322],{"class":256},[231,1973,1974],{"class":248}," Math",[231,1976,418],{"class":256},[231,1978,1979],{"class":244},"ceil",[231,1981,1982],{"class":248},"((count ",[231,1984,1960],{"class":296},[231,1986,1963],{"class":634},[231,1988,609],{"class":248},[231,1990,1991],{"class":296},"\u002F",[231,1993,1379],{"class":248},[231,1995,418],{"class":256},[231,1997,1998],{"class":248},"pageSize)",[231,2000,721],{"class":256},[231,2002,2003],{"class":233,"line":574},[231,2004,2005],{"class":256},"  },\n",[231,2007,2008],{"class":233,"line":587},[231,2009,1554],{"class":256},[45,2011,2012],{"id":2012},"單筆回應",[50,2014,2016],{"className":225,"code":2015,"language":227,"meta":59,"style":59},"interface SingleResponse&lt;T&gt; {\n  data: T\n}\n\n\u002F\u002F 新增操作：設定 201 狀態碼\nsetResponseStatus(event, 201)\nreturn { data: newResource }\n\n\u002F\u002F 查詢\u002F更新操作\nreturn { data: resource }\n",[57,2017,2018,2041,2050,2054,2058,2063,2076,2091,2095,2100],{"__ignoreMap":59},[231,2019,2020,2022,2025,2027,2029,2031,2033,2035,2037,2039],{"class":233,"line":234},[231,2021,1788],{"class":252},[231,2023,2024],{"class":1791}," SingleResponse",[231,2026,1795],{"class":248},[231,2028,1798],{"class":1791},[231,2030,1801],{"class":248},[231,2032,1804],{"class":1791},[231,2034,1795],{"class":248},[231,2036,1809],{"class":1791},[231,2038,1812],{"class":248},[231,2040,612],{"class":256},[231,2042,2043,2045,2047],{"class":233,"line":273},[231,2044,1820],{"class":1819},[231,2046,322],{"class":296},[231,2048,2049],{"class":1791}," T\n",[231,2051,2052],{"class":233,"line":280},[231,2053,1554],{"class":256},[231,2055,2056],{"class":233,"line":316},[231,2057,385],{"emptyLinePlaceholder":384},[231,2059,2060],{"class":233,"line":370},[231,2061,2062],{"class":276},"\u002F\u002F 新增操作：設定 201 狀態碼\n",[231,2064,2065,2068,2070,2072,2074],{"class":233,"line":381},[231,2066,2067],{"class":244},"setResponseStatus",[231,2069,1389],{"class":248},[231,2071,311],{"class":256},[231,2073,813],{"class":634},[231,2075,545],{"class":248},[231,2077,2078,2080,2082,2084,2086,2089],{"class":233,"line":388},[231,2079,1897],{"class":237},[231,2081,286],{"class":256},[231,2083,510],{"class":306},[231,2085,322],{"class":256},[231,2087,2088],{"class":248}," newResource ",[231,2090,1554],{"class":256},[231,2092,2093],{"class":233,"line":394},[231,2094,385],{"emptyLinePlaceholder":384},[231,2096,2097],{"class":233,"line":428},[231,2098,2099],{"class":276},"\u002F\u002F 查詢\u002F更新操作\n",[231,2101,2102,2104,2106,2108,2110,2113],{"class":233,"line":433},[231,2103,1897],{"class":237},[231,2105,286],{"class":256},[231,2107,510],{"class":306},[231,2109,322],{"class":256},[231,2111,2112],{"class":248}," resource ",[231,2114,1554],{"class":256},[45,2116,2117],{"id":2117},"刪除回應",[50,2119,2121],{"className":225,"code":2120,"language":227,"meta":59,"style":59},"interface DeleteResponse {\n  data: {\n    id: number;\n    deleted_at: string | null; \u002F\u002F 軟刪除時間\n    hard_deleted: boolean; \u002F\u002F 是否永久刪除\n  };\n}\n",[57,2122,2123,2132,2140,2152,2173,2188,2193],{"__ignoreMap":59},[231,2124,2125,2127,2130],{"class":233,"line":234},[231,2126,1788],{"class":252},[231,2128,2129],{"class":1791}," DeleteResponse",[231,2131,270],{"class":256},[231,2133,2134,2136,2138],{"class":233,"line":273},[231,2135,1820],{"class":1819},[231,2137,322],{"class":296},[231,2139,270],{"class":256},[231,2141,2142,2145,2147,2150],{"class":233,"line":280},[231,2143,2144],{"class":1819},"    id",[231,2146,322],{"class":296},[231,2148,2149],{"class":1847}," number",[231,2151,378],{"class":256},[231,2153,2154,2157,2159,2162,2165,2168,2170],{"class":233,"line":316},[231,2155,2156],{"class":1819},"    deleted_at",[231,2158,322],{"class":296},[231,2160,2161],{"class":1847}," string",[231,2163,2164],{"class":296}," |",[231,2166,2167],{"class":1847}," null",[231,2169,1801],{"class":256},[231,2171,2172],{"class":276}," \u002F\u002F 軟刪除時間\n",[231,2174,2175,2178,2180,2183,2185],{"class":233,"line":370},[231,2176,2177],{"class":1819},"    hard_deleted",[231,2179,322],{"class":296},[231,2181,2182],{"class":1847}," boolean",[231,2184,1801],{"class":256},[231,2186,2187],{"class":276}," \u002F\u002F 是否永久刪除\n",[231,2189,2190],{"class":233,"line":381},[231,2191,2192],{"class":256},"  };\n",[231,2194,2195],{"class":233,"line":388},[231,2196,1554],{"class":256},[38,2198],{},[11,2200,2201],{"id":2201},"錯誤處理",[45,2203,2204],{"id":2204},"錯誤類型與狀態碼",[64,2206,2207,2217],{},[67,2208,2209],{},[70,2210,2211,2214],{},[73,2212,2213],{},"狀態碼",[73,2215,2216],{},"使用情境",[83,2218,2219,2227,2235,2243,2251,2259],{},[70,2220,2221,2224],{},[88,2222,2223],{},"400",[88,2225,2226],{},"請求格式錯誤、驗證失敗",[70,2228,2229,2232],{},[88,2230,2231],{},"401",[88,2233,2234],{},"未認證（未登入）",[70,2236,2237,2240],{},[88,2238,2239],{},"403",[88,2241,2242],{},"無權限（已登入但權限不足）",[70,2244,2245,2248],{},[88,2246,2247],{},"404",[88,2249,2250],{},"資源不存在",[70,2252,2253,2256],{},[88,2254,2255],{},"409",[88,2257,2258],{},"資源衝突（如重複的 unique key）",[70,2260,2261,2263],{},[88,2262,1230],{},[88,2264,2265],{},"伺服器內部錯誤",[45,2267,2269],{"id":2268},"createerror-用法","createError 用法",[50,2271,2273],{"className":225,"code":2272,"language":227,"meta":59,"style":59},"\u002F\u002F 400 Bad Request\nthrow createError({\n  statusCode: 400,\n  statusMessage: \"Bad Request\",\n  message: \"缺少必要參數\",\n});\n\n\u002F\u002F 404 Not Found\nif (!data) {\n  throw createError({\n    statusCode: 404,\n    message: \"找不到指定的資源\",\n  });\n}\n\n\u002F\u002F 409 Conflict（唯一約束違反）\nif (error?.code === \"23505\") {\n  throw createError({\n    statusCode: 409,\n    message: \"資料重複，請檢查輸入\",\n  });\n}\n\n\u002F\u002F 500 Internal Server Error\nif (error) {\n  console.error(\"Database error:\", error);\n  throw createError({\n    statusCode: 500,\n    message: \"操作失敗，請稍後再試\",\n  });\n}\n",[57,2274,2275,2280,2291,2302,2317,2333,2341,2345,2350,2365,2376,2388,2404,2412,2416,2420,2425,2452,2462,2473,2488,2496,2500,2504,2509,2518,2544,2554,2564,2579,2587],{"__ignoreMap":59},[231,2276,2277],{"class":233,"line":234},[231,2278,2279],{"class":276},"\u002F\u002F 400 Bad Request\n",[231,2281,2282,2285,2287,2289],{"class":233,"line":273},[231,2283,2284],{"class":237},"throw",[231,2286,621],{"class":244},[231,2288,249],{"class":248},[231,2290,612],{"class":256},[231,2292,2293,2296,2298,2300],{"class":233,"line":280},[231,2294,2295],{"class":306},"  statusCode",[231,2297,322],{"class":256},[231,2299,1509],{"class":634},[231,2301,721],{"class":256},[231,2303,2304,2307,2309,2311,2313,2315],{"class":233,"line":316},[231,2305,2306],{"class":306},"  statusMessage",[231,2308,322],{"class":256},[231,2310,347],{"class":335},[231,2312,1527],{"class":339},[231,2314,336],{"class":335},[231,2316,721],{"class":256},[231,2318,2319,2322,2324,2326,2329,2331],{"class":233,"line":370},[231,2320,2321],{"class":306},"  message",[231,2323,322],{"class":256},[231,2325,347],{"class":335},[231,2327,2328],{"class":339},"缺少必要參數",[231,2330,336],{"class":335},[231,2332,721],{"class":256},[231,2334,2335,2337,2339],{"class":233,"line":381},[231,2336,836],{"class":256},[231,2338,264],{"class":248},[231,2340,378],{"class":256},[231,2342,2343],{"class":233,"line":388},[231,2344,385],{"emptyLinePlaceholder":384},[231,2346,2347],{"class":233,"line":394},[231,2348,2349],{"class":276},"\u002F\u002F 404 Not Found\n",[231,2351,2352,2355,2357,2360,2363],{"class":233,"line":428},[231,2353,2354],{"class":237},"if",[231,2356,257],{"class":248},[231,2358,2359],{"class":296},"!",[231,2361,2362],{"class":248},"data) ",[231,2364,612],{"class":256},[231,2366,2367,2370,2372,2374],{"class":233,"line":433},[231,2368,2369],{"class":237},"  throw",[231,2371,621],{"class":244},[231,2373,249],{"class":306},[231,2375,612],{"class":256},[231,2377,2378,2381,2383,2386],{"class":233,"line":439},[231,2379,2380],{"class":306},"    statusCode",[231,2382,322],{"class":256},[231,2384,2385],{"class":634}," 404",[231,2387,721],{"class":256},[231,2389,2390,2393,2395,2397,2400,2402],{"class":233,"line":462},[231,2391,2392],{"class":306},"    message",[231,2394,322],{"class":256},[231,2396,347],{"class":335},[231,2398,2399],{"class":339},"找不到指定的資源",[231,2401,336],{"class":335},[231,2403,721],{"class":256},[231,2405,2406,2408,2410],{"class":233,"line":492},[231,2407,373],{"class":256},[231,2409,264],{"class":306},[231,2411,378],{"class":256},[231,2413,2414],{"class":233,"line":497},[231,2415,1554],{"class":256},[231,2417,2418],{"class":233,"line":503},[231,2419,385],{"emptyLinePlaceholder":384},[231,2421,2422],{"class":233,"line":527},[231,2423,2424],{"class":276},"\u002F\u002F 409 Conflict（唯一約束違反）\n",[231,2426,2427,2429,2432,2435,2438,2441,2443,2446,2448,2450],{"class":233,"line":548},[231,2428,2354],{"class":237},[231,2430,2431],{"class":248}," (error",[231,2433,2434],{"class":256},"?.",[231,2436,2437],{"class":248},"code ",[231,2439,2440],{"class":296},"===",[231,2442,347],{"class":335},[231,2444,2445],{"class":339},"23505",[231,2447,336],{"class":335},[231,2449,609],{"class":248},[231,2451,612],{"class":256},[231,2453,2454,2456,2458,2460],{"class":233,"line":563},[231,2455,2369],{"class":237},[231,2457,621],{"class":244},[231,2459,249],{"class":306},[231,2461,612],{"class":256},[231,2463,2464,2466,2468,2471],{"class":233,"line":574},[231,2465,2380],{"class":306},[231,2467,322],{"class":256},[231,2469,2470],{"class":634}," 409",[231,2472,721],{"class":256},[231,2474,2475,2477,2479,2481,2484,2486],{"class":233,"line":587},[231,2476,2392],{"class":306},[231,2478,322],{"class":256},[231,2480,347],{"class":335},[231,2482,2483],{"class":339},"資料重複，請檢查輸入",[231,2485,336],{"class":335},[231,2487,721],{"class":256},[231,2489,2490,2492,2494],{"class":233,"line":592},[231,2491,373],{"class":256},[231,2493,264],{"class":306},[231,2495,378],{"class":256},[231,2497,2498],{"class":233,"line":598},[231,2499,1554],{"class":256},[231,2501,2502],{"class":233,"line":615},[231,2503,385],{"emptyLinePlaceholder":384},[231,2505,2506],{"class":233,"line":658},[231,2507,2508],{"class":276},"\u002F\u002F 500 Internal Server Error\n",[231,2510,2511,2513,2516],{"class":233,"line":664},[231,2512,2354],{"class":237},[231,2514,2515],{"class":248}," (error) ",[231,2517,612],{"class":256},[231,2519,2520,2523,2525,2527,2529,2531,2534,2536,2538,2540,2542],{"class":233,"line":669},[231,2521,2522],{"class":248},"  console",[231,2524,418],{"class":256},[231,2526,606],{"class":244},[231,2528,249],{"class":306},[231,2530,336],{"class":335},[231,2532,2533],{"class":339},"Database error:",[231,2535,336],{"class":335},[231,2537,311],{"class":256},[231,2539,515],{"class":248},[231,2541,264],{"class":306},[231,2543,378],{"class":256},[231,2545,2546,2548,2550,2552],{"class":233,"line":675},[231,2547,2369],{"class":237},[231,2549,621],{"class":244},[231,2551,249],{"class":306},[231,2553,612],{"class":256},[231,2555,2556,2558,2560,2562],{"class":233,"line":706},[231,2557,2380],{"class":306},[231,2559,322],{"class":256},[231,2561,635],{"class":634},[231,2563,721],{"class":256},[231,2565,2566,2568,2570,2572,2575,2577],{"class":233,"line":724},[231,2567,2392],{"class":306},[231,2569,322],{"class":256},[231,2571,347],{"class":335},[231,2573,2574],{"class":339},"操作失敗，請稍後再試",[231,2576,336],{"class":335},[231,2578,721],{"class":256},[231,2580,2581,2583,2585],{"class":233,"line":741},[231,2582,373],{"class":256},[231,2584,264],{"class":306},[231,2586,378],{"class":256},[231,2588,2589],{"class":233,"line":758},[231,2590,1554],{"class":256},[38,2592],{},[11,2594,2595],{"id":2595},"搜尋與排序",[45,2597,2598],{"id":2598},"搜尋實作",[50,2600,2602],{"className":225,"code":2601,"language":227,"meta":59,"style":59},"\u002F\u002F 多欄位搜尋（使用 OR 條件）\nif (query.search) {\n  const searchStr = `%${query.search}%`;\n  dbQuery = dbQuery.or(\n    `name.ilike.${searchStr},description.ilike.${searchStr}`,\n  );\n}\n",[57,2603,2604,2609,2623,2658,2676,2703,2710],{"__ignoreMap":59},[231,2605,2606],{"class":233,"line":234},[231,2607,2608],{"class":276},"\u002F\u002F 多欄位搜尋（使用 OR 條件）\n",[231,2610,2611,2613,2616,2618,2621],{"class":233,"line":273},[231,2612,2354],{"class":237},[231,2614,2615],{"class":248}," (query",[231,2617,418],{"class":256},[231,2619,2620],{"class":248},"search) ",[231,2622,612],{"class":256},[231,2624,2625,2627,2630,2632,2635,2638,2641,2644,2646,2649,2651,2653,2656],{"class":233,"line":280},[231,2626,283],{"class":252},[231,2628,2629],{"class":289}," searchStr",[231,2631,297],{"class":296},[231,2633,2634],{"class":335}," `",[231,2636,2637],{"class":339},"%",[231,2639,2640],{"class":335},"${",[231,2642,2643],{"class":248},"query",[231,2645,418],{"class":335},[231,2647,2648],{"class":248},"search",[231,2650,836],{"class":335},[231,2652,2637],{"class":339},[231,2654,2655],{"class":335},"`",[231,2657,378],{"class":256},[231,2659,2660,2663,2665,2668,2670,2673],{"class":233,"line":316},[231,2661,2662],{"class":248},"  dbQuery",[231,2664,297],{"class":296},[231,2666,2667],{"class":248}," dbQuery",[231,2669,418],{"class":256},[231,2671,2672],{"class":244},"or",[231,2674,2675],{"class":306},"(\n",[231,2677,2678,2681,2684,2686,2689,2691,2694,2696,2698,2701],{"class":233,"line":370},[231,2679,2680],{"class":335},"    `",[231,2682,2683],{"class":339},"name.ilike.",[231,2685,2640],{"class":335},[231,2687,2688],{"class":248},"searchStr",[231,2690,836],{"class":335},[231,2692,2693],{"class":339},",description.ilike.",[231,2695,2640],{"class":335},[231,2697,2688],{"class":248},[231,2699,2700],{"class":335},"}`",[231,2702,721],{"class":256},[231,2704,2705,2708],{"class":233,"line":381},[231,2706,2707],{"class":306},"  )",[231,2709,378],{"class":256},[231,2711,2712],{"class":233,"line":388},[231,2713,1554],{"class":256},[45,2715,2716],{"id":2716},"排序實作",[50,2718,2720],{"className":225,"code":2719,"language":227,"meta":59,"style":59},"\u002F\u002F 動態排序\nconst sortDirAsc = query.sortDir === \"asc\";\ndbQuery = dbQuery.order(query.sortBy || \"id\", { ascending: sortDirAsc });\n",[57,2721,2722,2727,2753],{"__ignoreMap":59},[231,2723,2724],{"class":233,"line":234},[231,2725,2726],{"class":276},"\u002F\u002F 動態排序\n",[231,2728,2729,2731,2734,2736,2738,2740,2743,2745,2747,2749,2751],{"class":233,"line":273},[231,2730,1376],{"class":252},[231,2732,2733],{"class":289}," sortDirAsc",[231,2735,297],{"class":296},[231,2737,1379],{"class":248},[231,2739,418],{"class":256},[231,2741,2742],{"class":248},"sortDir ",[231,2744,2440],{"class":296},[231,2746,347],{"class":335},[231,2748,1091],{"class":339},[231,2750,336],{"class":335},[231,2752,378],{"class":256},[231,2754,2755,2758,2761,2763,2765,2768,2771,2773,2776,2778,2780,2782,2784,2786,2788,2791,2793,2796,2798,2800],{"class":233,"line":280},[231,2756,2757],{"class":248},"dbQuery ",[231,2759,2760],{"class":296},"=",[231,2762,2667],{"class":248},[231,2764,418],{"class":256},[231,2766,2767],{"class":244},"order",[231,2769,2770],{"class":248},"(query",[231,2772,418],{"class":256},[231,2774,2775],{"class":248},"sortBy ",[231,2777,1960],{"class":296},[231,2779,347],{"class":335},[231,2781,718],{"class":339},[231,2783,336],{"class":335},[231,2785,311],{"class":256},[231,2787,286],{"class":256},[231,2789,2790],{"class":306}," ascending",[231,2792,322],{"class":256},[231,2794,2795],{"class":248}," sortDirAsc ",[231,2797,836],{"class":256},[231,2799,264],{"class":248},[231,2801,378],{"class":256},[45,2803,2804],{"id":2804},"分頁實作",[50,2806,2808],{"className":225,"code":2807,"language":227,"meta":59,"style":59},"\u002F\u002F 計算 range\nconst from = (query.page - 1) * query.pageSize;\nconst to = from + query.pageSize - 1;\n\n\u002F\u002F 執行分頁查詢\nconst { data, count, error } = await dbQuery\n  .select(\"*\", { count: \"exact\" })\n  .range(from, to);\n",[57,2809,2810,2815,2849,2877,2881,2886,2912,2946],{"__ignoreMap":59},[231,2811,2812],{"class":233,"line":234},[231,2813,2814],{"class":276},"\u002F\u002F 計算 range\n",[231,2816,2817,2819,2821,2823,2825,2827,2830,2833,2836,2838,2841,2843,2845,2847],{"class":233,"line":273},[231,2818,1376],{"class":252},[231,2820,875],{"class":289},[231,2822,297],{"class":296},[231,2824,2615],{"class":248},[231,2826,418],{"class":256},[231,2828,2829],{"class":248},"page ",[231,2831,2832],{"class":296},"-",[231,2834,2835],{"class":634}," 1",[231,2837,609],{"class":248},[231,2839,2840],{"class":296},"*",[231,2842,1379],{"class":248},[231,2844,418],{"class":256},[231,2846,1946],{"class":248},[231,2848,378],{"class":256},[231,2850,2851,2853,2856,2858,2861,2864,2866,2868,2871,2873,2875],{"class":233,"line":280},[231,2852,1376],{"class":252},[231,2854,2855],{"class":289}," to",[231,2857,297],{"class":296},[231,2859,2860],{"class":248}," from ",[231,2862,2863],{"class":296},"+",[231,2865,1379],{"class":248},[231,2867,418],{"class":256},[231,2869,2870],{"class":248},"pageSize ",[231,2872,2832],{"class":296},[231,2874,2835],{"class":634},[231,2876,378],{"class":256},[231,2878,2879],{"class":233,"line":316},[231,2880,385],{"emptyLinePlaceholder":384},[231,2882,2883],{"class":233,"line":370},[231,2884,2885],{"class":276},"\u002F\u002F 執行分頁查詢\n",[231,2887,2888,2890,2892,2894,2896,2899,2901,2903,2905,2907,2909],{"class":233,"line":381},[231,2889,1376],{"class":252},[231,2891,286],{"class":256},[231,2893,510],{"class":289},[231,2895,311],{"class":256},[231,2897,2898],{"class":289}," count",[231,2900,311],{"class":256},[231,2902,515],{"class":289},[231,2904,293],{"class":256},[231,2906,297],{"class":296},[231,2908,300],{"class":237},[231,2910,2911],{"class":248}," dbQuery\n",[231,2913,2914,2917,2919,2921,2923,2925,2927,2929,2931,2933,2935,2937,2940,2942,2944],{"class":233,"line":388},[231,2915,2916],{"class":256},"  .",[231,2918,568],{"class":244},[231,2920,249],{"class":248},[231,2922,336],{"class":335},[231,2924,2840],{"class":339},[231,2926,336],{"class":335},[231,2928,311],{"class":256},[231,2930,286],{"class":256},[231,2932,2898],{"class":306},[231,2934,322],{"class":256},[231,2936,347],{"class":335},[231,2938,2939],{"class":339},"exact",[231,2941,336],{"class":335},[231,2943,293],{"class":256},[231,2945,545],{"class":248},[231,2947,2948,2950,2953,2956,2958,2961],{"class":233,"line":394},[231,2949,2916],{"class":256},[231,2951,2952],{"class":244},"range",[231,2954,2955],{"class":248},"(from",[231,2957,311],{"class":256},[231,2959,2960],{"class":248}," to)",[231,2962,378],{"class":256},[38,2964],{},[11,2966,2968],{"id":2967},"完整-api-範例","完整 API 範例",[45,2970,2972],{"id":2971},"get-列表-api","GET 列表 API",[50,2974,2976],{"className":225,"code":2975,"language":227,"meta":59,"style":59},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fresources\u002Findex.get.ts\nimport { getSupabaseWithContext } from \"~~\u002Fserver\u002Futils\u002Fsupabase\";\nimport { paginationQuerySchema } from \"~~\u002Fshared\u002Ftypes\u002Fresources\";\n\nexport default defineEventHandler(async (event) => {\n  \u002F\u002F 1. 權限檢查\n  await requireUserSession(event);\n\n  \u002F\u002F 2. 驗證查詢參數\n  const query = await getValidatedQuery(event, paginationQuerySchema.parse);\n\n  \u002F\u002F 3. 取得 Supabase Client\n  const supabase = await getSupabaseWithContext(event);\n  const db = supabase.schema(\"your_schema\");\n\n  \u002F\u002F 4. 建立查詢\n  let dbQuery = db\n    .from(\"resources\")\n    .select(\"*\", { count: \"exact\" })\n    .is(\"deleted_at\", null);\n\n  \u002F\u002F 5. 搜尋條件\n  if (query.search) {\n    const searchStr = `%${query.search}%`;\n    dbQuery = dbQuery.or(`name.ilike.${searchStr}`);\n  }\n\n  \u002F\u002F 6. 排序\n  dbQuery = dbQuery.order(query.sortBy || \"id\", {\n    ascending: query.sortDir === \"asc\",\n  });\n\n  \u002F\u002F 7. 分頁\n  const from = (query.page - 1) * query.pageSize;\n  const to = from + query.pageSize - 1;\n  const { data, count, error } = await dbQuery.range(from, to);\n\n  \u002F\u002F 8. 錯誤處理\n  if (error) {\n    throw createError({ statusCode: 500, message: \"載入資料失敗\" });\n  }\n\n  \u002F\u002F 9. 回應\n  return {\n    data: data || [],\n    pagination: {\n      page: query.page,\n      pageSize: query.pageSize,\n      total: count || 0,\n      totalPages: Math.ceil((count || 0) \u002F query.pageSize),\n    },\n  };\n});\n",[57,2977,2978,2983,3004,3025,3029,3051,3056,3070,3074,3079,3107,3111,3115,3135,3161,3165,3170,3181,3197,3229,3253,3257,3262,3278,3307,3336,3340,3344,3349,3383,3408,3416,3420,3425,3458,3483,3523,3527,3533,3546,3582,3587,3592,3598,3605,3622,3632,3648,3664,3680,3718,3724,3729],{"__ignoreMap":59},[231,2979,2980],{"class":233,"line":234},[231,2981,2982],{"class":276},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fresources\u002Findex.get.ts\n",[231,2984,2985,2987,2989,2991,2993,2995,2997,3000,3002],{"class":233,"line":273},[231,2986,865],{"class":237},[231,2988,286],{"class":256},[231,2990,451],{"class":248},[231,2992,293],{"class":256},[231,2994,875],{"class":237},[231,2996,347],{"class":335},[231,2998,2999],{"class":339},"~~\u002Fserver\u002Futils\u002Fsupabase",[231,3001,336],{"class":335},[231,3003,378],{"class":256},[231,3005,3006,3008,3010,3012,3014,3016,3018,3021,3023],{"class":233,"line":280},[231,3007,865],{"class":237},[231,3009,286],{"class":256},[231,3011,903],{"class":248},[231,3013,293],{"class":256},[231,3015,875],{"class":237},[231,3017,347],{"class":335},[231,3019,3020],{"class":339},"~~\u002Fshared\u002Ftypes\u002Fresources",[231,3022,336],{"class":335},[231,3024,378],{"class":256},[231,3026,3027],{"class":233,"line":316},[231,3028,385],{"emptyLinePlaceholder":384},[231,3030,3031,3033,3035,3037,3039,3041,3043,3045,3047,3049],{"class":233,"line":370},[231,3032,238],{"class":237},[231,3034,241],{"class":237},[231,3036,245],{"class":244},[231,3038,249],{"class":248},[231,3040,253],{"class":252},[231,3042,257],{"class":256},[231,3044,261],{"class":260},[231,3046,264],{"class":256},[231,3048,267],{"class":252},[231,3050,270],{"class":256},[231,3052,3053],{"class":233,"line":381},[231,3054,3055],{"class":276},"  \u002F\u002F 1. 權限檢查\n",[231,3057,3058,3060,3062,3064,3066,3068],{"class":233,"line":388},[231,3059,678],{"class":237},[231,3061,303],{"class":244},[231,3063,249],{"class":306},[231,3065,261],{"class":248},[231,3067,264],{"class":306},[231,3069,378],{"class":256},[231,3071,3072],{"class":233,"line":394},[231,3073,385],{"emptyLinePlaceholder":384},[231,3075,3076],{"class":233,"line":428},[231,3077,3078],{"class":276},"  \u002F\u002F 2. 驗證查詢參數\n",[231,3080,3081,3083,3085,3087,3089,3091,3093,3095,3097,3099,3101,3103,3105],{"class":233,"line":433},[231,3082,283],{"class":252},[231,3084,1379],{"class":289},[231,3086,297],{"class":296},[231,3088,300],{"class":237},[231,3090,1386],{"class":244},[231,3092,249],{"class":306},[231,3094,261],{"class":248},[231,3096,311],{"class":256},[231,3098,903],{"class":248},[231,3100,418],{"class":256},[231,3102,421],{"class":248},[231,3104,264],{"class":306},[231,3106,378],{"class":256},[231,3108,3109],{"class":233,"line":439},[231,3110,385],{"emptyLinePlaceholder":384},[231,3112,3113],{"class":233,"line":462},[231,3114,436],{"class":276},[231,3116,3117,3119,3121,3123,3125,3127,3129,3131,3133],{"class":233,"line":492},[231,3118,283],{"class":252},[231,3120,444],{"class":289},[231,3122,297],{"class":296},[231,3124,300],{"class":237},[231,3126,451],{"class":244},[231,3128,249],{"class":306},[231,3130,261],{"class":248},[231,3132,264],{"class":306},[231,3134,378],{"class":256},[231,3136,3137,3139,3141,3143,3145,3147,3149,3151,3153,3155,3157,3159],{"class":233,"line":497},[231,3138,283],{"class":252},[231,3140,467],{"class":289},[231,3142,297],{"class":296},[231,3144,444],{"class":248},[231,3146,418],{"class":256},[231,3148,476],{"class":244},[231,3150,249],{"class":306},[231,3152,336],{"class":335},[231,3154,483],{"class":339},[231,3156,336],{"class":335},[231,3158,264],{"class":306},[231,3160,378],{"class":256},[231,3162,3163],{"class":233,"line":503},[231,3164,385],{"emptyLinePlaceholder":384},[231,3166,3167],{"class":233,"line":527},[231,3168,3169],{"class":276},"  \u002F\u002F 4. 建立查詢\n",[231,3171,3172,3175,3177,3179],{"class":233,"line":548},[231,3173,3174],{"class":252},"  let",[231,3176,2667],{"class":248},[231,3178,297],{"class":296},[231,3180,524],{"class":248},[231,3182,3183,3185,3187,3189,3191,3193,3195],{"class":233,"line":563},[231,3184,530],{"class":256},[231,3186,533],{"class":244},[231,3188,249],{"class":306},[231,3190,336],{"class":335},[231,3192,540],{"class":339},[231,3194,336],{"class":335},[231,3196,545],{"class":306},[231,3198,3199,3201,3203,3205,3207,3209,3211,3213,3215,3217,3219,3221,3223,3225,3227],{"class":233,"line":574},[231,3200,530],{"class":256},[231,3202,568],{"class":244},[231,3204,249],{"class":306},[231,3206,336],{"class":335},[231,3208,2840],{"class":339},[231,3210,336],{"class":335},[231,3212,311],{"class":256},[231,3214,286],{"class":256},[231,3216,2898],{"class":306},[231,3218,322],{"class":256},[231,3220,347],{"class":335},[231,3222,2939],{"class":339},[231,3224,336],{"class":335},[231,3226,293],{"class":256},[231,3228,545],{"class":306},[231,3230,3231,3233,3236,3238,3240,3243,3245,3247,3249,3251],{"class":233,"line":587},[231,3232,530],{"class":256},[231,3234,3235],{"class":244},"is",[231,3237,249],{"class":306},[231,3239,336],{"class":335},[231,3241,3242],{"class":339},"deleted_at",[231,3244,336],{"class":335},[231,3246,311],{"class":256},[231,3248,2167],{"class":1497},[231,3250,264],{"class":306},[231,3252,378],{"class":256},[231,3254,3255],{"class":233,"line":592},[231,3256,385],{"emptyLinePlaceholder":384},[231,3258,3259],{"class":233,"line":598},[231,3260,3261],{"class":276},"  \u002F\u002F 5. 搜尋條件\n",[231,3263,3264,3266,3268,3270,3272,3274,3276],{"class":233,"line":615},[231,3265,601],{"class":237},[231,3267,257],{"class":306},[231,3269,2643],{"class":248},[231,3271,418],{"class":256},[231,3273,2648],{"class":248},[231,3275,609],{"class":306},[231,3277,612],{"class":256},[231,3279,3280,3283,3285,3287,3289,3291,3293,3295,3297,3299,3301,3303,3305],{"class":233,"line":658},[231,3281,3282],{"class":252},"    const",[231,3284,2629],{"class":289},[231,3286,297],{"class":296},[231,3288,2634],{"class":335},[231,3290,2637],{"class":339},[231,3292,2640],{"class":335},[231,3294,2643],{"class":248},[231,3296,418],{"class":335},[231,3298,2648],{"class":248},[231,3300,836],{"class":335},[231,3302,2637],{"class":339},[231,3304,2655],{"class":335},[231,3306,378],{"class":256},[231,3308,3309,3312,3314,3316,3318,3320,3322,3324,3326,3328,3330,3332,3334],{"class":233,"line":664},[231,3310,3311],{"class":248},"    dbQuery",[231,3313,297],{"class":296},[231,3315,2667],{"class":248},[231,3317,418],{"class":256},[231,3319,2672],{"class":244},[231,3321,249],{"class":306},[231,3323,2655],{"class":335},[231,3325,2683],{"class":339},[231,3327,2640],{"class":335},[231,3329,2688],{"class":248},[231,3331,2700],{"class":335},[231,3333,264],{"class":306},[231,3335,378],{"class":256},[231,3337,3338],{"class":233,"line":669},[231,3339,661],{"class":256},[231,3341,3342],{"class":233,"line":675},[231,3343,385],{"emptyLinePlaceholder":384},[231,3345,3346],{"class":233,"line":706},[231,3347,3348],{"class":276},"  \u002F\u002F 6. 排序\n",[231,3350,3351,3353,3355,3357,3359,3361,3363,3365,3367,3370,3373,3375,3377,3379,3381],{"class":233,"line":724},[231,3352,2662],{"class":248},[231,3354,297],{"class":296},[231,3356,2667],{"class":248},[231,3358,418],{"class":256},[231,3360,2767],{"class":244},[231,3362,249],{"class":306},[231,3364,2643],{"class":248},[231,3366,418],{"class":256},[231,3368,3369],{"class":248},"sortBy",[231,3371,3372],{"class":296}," ||",[231,3374,347],{"class":335},[231,3376,718],{"class":339},[231,3378,336],{"class":335},[231,3380,311],{"class":256},[231,3382,270],{"class":256},[231,3384,3385,3388,3390,3392,3394,3397,3400,3402,3404,3406],{"class":233,"line":741},[231,3386,3387],{"class":306},"    ascending",[231,3389,322],{"class":256},[231,3391,1379],{"class":248},[231,3393,418],{"class":256},[231,3395,3396],{"class":248},"sortDir",[231,3398,3399],{"class":296}," ===",[231,3401,347],{"class":335},[231,3403,1091],{"class":339},[231,3405,336],{"class":335},[231,3407,721],{"class":256},[231,3409,3410,3412,3414],{"class":233,"line":758},[231,3411,373],{"class":256},[231,3413,264],{"class":306},[231,3415,378],{"class":256},[231,3417,3418],{"class":233,"line":781},[231,3419,385],{"emptyLinePlaceholder":384},[231,3421,3422],{"class":233,"line":790},[231,3423,3424],{"class":276},"  \u002F\u002F 7. 分頁\n",[231,3426,3427,3429,3431,3433,3435,3437,3439,3441,3444,3446,3448,3450,3452,3454,3456],{"class":233,"line":795},[231,3428,283],{"class":252},[231,3430,875],{"class":289},[231,3432,297],{"class":296},[231,3434,257],{"class":306},[231,3436,2643],{"class":248},[231,3438,418],{"class":256},[231,3440,1931],{"class":248},[231,3442,3443],{"class":296}," -",[231,3445,2835],{"class":634},[231,3447,609],{"class":306},[231,3449,2840],{"class":296},[231,3451,1379],{"class":248},[231,3453,418],{"class":256},[231,3455,1946],{"class":248},[231,3457,378],{"class":256},[231,3459,3460,3462,3464,3466,3468,3471,3473,3475,3477,3479,3481],{"class":233,"line":801},[231,3461,283],{"class":252},[231,3463,2855],{"class":289},[231,3465,297],{"class":296},[231,3467,875],{"class":248},[231,3469,3470],{"class":296}," +",[231,3472,1379],{"class":248},[231,3474,418],{"class":256},[231,3476,1946],{"class":248},[231,3478,3443],{"class":296},[231,3480,2835],{"class":634},[231,3482,378],{"class":256},[231,3484,3485,3487,3489,3491,3493,3495,3497,3499,3501,3503,3505,3507,3509,3511,3513,3515,3517,3519,3521],{"class":233,"line":820},[231,3486,283],{"class":252},[231,3488,286],{"class":256},[231,3490,510],{"class":289},[231,3492,311],{"class":256},[231,3494,2898],{"class":289},[231,3496,311],{"class":256},[231,3498,515],{"class":289},[231,3500,293],{"class":256},[231,3502,297],{"class":296},[231,3504,300],{"class":237},[231,3506,2667],{"class":248},[231,3508,418],{"class":256},[231,3510,2952],{"class":244},[231,3512,249],{"class":306},[231,3514,533],{"class":248},[231,3516,311],{"class":256},[231,3518,2855],{"class":248},[231,3520,264],{"class":306},[231,3522,378],{"class":256},[231,3524,3525],{"class":233,"line":833},[231,3526,385],{"emptyLinePlaceholder":384},[231,3528,3530],{"class":233,"line":3529},38,[231,3531,3532],{"class":276},"  \u002F\u002F 8. 錯誤處理\n",[231,3534,3536,3538,3540,3542,3544],{"class":233,"line":3535},39,[231,3537,601],{"class":237},[231,3539,257],{"class":306},[231,3541,606],{"class":248},[231,3543,609],{"class":306},[231,3545,612],{"class":256},[231,3547,3549,3551,3553,3555,3557,3559,3561,3563,3565,3567,3569,3571,3574,3576,3578,3580],{"class":233,"line":3548},40,[231,3550,618],{"class":237},[231,3552,621],{"class":244},[231,3554,249],{"class":306},[231,3556,626],{"class":256},[231,3558,629],{"class":306},[231,3560,322],{"class":256},[231,3562,635],{"class":634},[231,3564,311],{"class":256},[231,3566,640],{"class":306},[231,3568,322],{"class":256},[231,3570,347],{"class":335},[231,3572,3573],{"class":339},"載入資料失敗",[231,3575,336],{"class":335},[231,3577,293],{"class":256},[231,3579,264],{"class":306},[231,3581,378],{"class":256},[231,3583,3585],{"class":233,"line":3584},41,[231,3586,661],{"class":256},[231,3588,3590],{"class":233,"line":3589},42,[231,3591,385],{"emptyLinePlaceholder":384},[231,3593,3595],{"class":233,"line":3594},43,[231,3596,3597],{"class":276},"  \u002F\u002F 9. 回應\n",[231,3599,3601,3603],{"class":233,"line":3600},44,[231,3602,823],{"class":237},[231,3604,270],{"class":256},[231,3606,3608,3611,3613,3615,3617,3620],{"class":233,"line":3607},45,[231,3609,3610],{"class":306},"    data",[231,3612,322],{"class":256},[231,3614,510],{"class":248},[231,3616,3372],{"class":296},[231,3618,3619],{"class":306}," []",[231,3621,721],{"class":256},[231,3623,3625,3628,3630],{"class":233,"line":3624},46,[231,3626,3627],{"class":306},"    pagination",[231,3629,322],{"class":256},[231,3631,270],{"class":256},[231,3633,3635,3638,3640,3642,3644,3646],{"class":233,"line":3634},47,[231,3636,3637],{"class":306},"      page",[231,3639,322],{"class":256},[231,3641,1379],{"class":248},[231,3643,418],{"class":256},[231,3645,1931],{"class":248},[231,3647,721],{"class":256},[231,3649,3651,3654,3656,3658,3660,3662],{"class":233,"line":3650},48,[231,3652,3653],{"class":306},"      pageSize",[231,3655,322],{"class":256},[231,3657,1379],{"class":248},[231,3659,418],{"class":256},[231,3661,1946],{"class":248},[231,3663,721],{"class":256},[231,3665,3667,3670,3672,3674,3676,3678],{"class":233,"line":3666},49,[231,3668,3669],{"class":306},"      total",[231,3671,322],{"class":256},[231,3673,2898],{"class":248},[231,3675,3372],{"class":296},[231,3677,1963],{"class":634},[231,3679,721],{"class":256},[231,3681,3683,3686,3688,3690,3692,3694,3697,3700,3702,3704,3706,3708,3710,3712,3714,3716],{"class":233,"line":3682},50,[231,3684,3685],{"class":306},"      totalPages",[231,3687,322],{"class":256},[231,3689,1974],{"class":248},[231,3691,418],{"class":256},[231,3693,1979],{"class":244},[231,3695,3696],{"class":306},"((",[231,3698,3699],{"class":248},"count",[231,3701,3372],{"class":296},[231,3703,1963],{"class":634},[231,3705,609],{"class":306},[231,3707,1991],{"class":296},[231,3709,1379],{"class":248},[231,3711,418],{"class":256},[231,3713,1946],{"class":248},[231,3715,264],{"class":306},[231,3717,721],{"class":256},[231,3719,3721],{"class":233,"line":3720},51,[231,3722,3723],{"class":256},"    },\n",[231,3725,3727],{"class":233,"line":3726},52,[231,3728,2192],{"class":256},[231,3730,3732,3734,3736],{"class":233,"line":3731},53,[231,3733,836],{"class":256},[231,3735,264],{"class":248},[231,3737,378],{"class":256},[45,3739,3741],{"id":3740},"post-新增-api","POST 新增 API",[50,3743,3745],{"className":225,"code":3744,"language":227,"meta":59,"style":59},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fresources\u002Findex.post.ts\nimport { getSupabaseWithContext } from \"~~\u002Fserver\u002Futils\u002Fsupabase\";\nimport { createResourceSchema } from \"~~\u002Fshared\u002Ftypes\u002Fresources\";\n\nexport default defineEventHandler(async (event) => {\n  \u002F\u002F 1. 權限檢查\n  const { user } = await requireUserSession(event, {\n    user: { role: [\"admin\", \"manager\"] },\n  });\n\n  \u002F\u002F 2. 驗證請求資料\n  const body = await readValidatedBody(event, createResourceSchema.parse);\n\n  \u002F\u002F 3. 取得 Supabase Client\n  const supabase = await getSupabaseWithContext(event);\n  const db = supabase.schema(\"your_schema\");\n\n  \u002F\u002F 4. 新增資料\n  const { data, error } = await db\n    .from(\"resources\")\n    .insert({ ...body, created_by: user.id })\n    .select()\n    .single();\n\n  \u002F\u002F 5. 錯誤處理\n  if (error?.code === \"23505\") {\n    throw createError({ statusCode: 409, message: \"資料重複\" });\n  }\n  if (error) {\n    throw createError({ statusCode: 500, message: \"新增失敗\" });\n  }\n\n  \u002F\u002F 6. 記錄操作日誌\n  await db.from(\"operation_logs\").insert({\n    user_id: user.id,\n    action: \"create\",\n    target_type: \"resource\",\n    target_id: data.id.toString(),\n    details: body,\n  });\n\n  \u002F\u002F 7. 回應\n  setResponseStatus(event, 201);\n  return { data };\n});\n",[57,3746,3747,3752,3772,3792,3796,3818,3822,3846,3878,3886,3890,3895,3923,3927,3931,3951,3977,3981,3986,4006,4022,4054,4062,4072,4076,4080,4104,4139,4143,4155,4190,4194,4198,4203,4231,4245,4259,4273,4293,4304,4312,4316,4321,4337,4347],{"__ignoreMap":59},[231,3748,3749],{"class":233,"line":234},[231,3750,3751],{"class":276},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fresources\u002Findex.post.ts\n",[231,3753,3754,3756,3758,3760,3762,3764,3766,3768,3770],{"class":233,"line":273},[231,3755,865],{"class":237},[231,3757,286],{"class":256},[231,3759,451],{"class":248},[231,3761,293],{"class":256},[231,3763,875],{"class":237},[231,3765,347],{"class":335},[231,3767,2999],{"class":339},[231,3769,336],{"class":335},[231,3771,378],{"class":256},[231,3773,3774,3776,3778,3780,3782,3784,3786,3788,3790],{"class":233,"line":280},[231,3775,865],{"class":237},[231,3777,286],{"class":256},[231,3779,415],{"class":248},[231,3781,293],{"class":256},[231,3783,875],{"class":237},[231,3785,347],{"class":335},[231,3787,3020],{"class":339},[231,3789,336],{"class":335},[231,3791,378],{"class":256},[231,3793,3794],{"class":233,"line":316},[231,3795,385],{"emptyLinePlaceholder":384},[231,3797,3798,3800,3802,3804,3806,3808,3810,3812,3814,3816],{"class":233,"line":370},[231,3799,238],{"class":237},[231,3801,241],{"class":237},[231,3803,245],{"class":244},[231,3805,249],{"class":248},[231,3807,253],{"class":252},[231,3809,257],{"class":256},[231,3811,261],{"class":260},[231,3813,264],{"class":256},[231,3815,267],{"class":252},[231,3817,270],{"class":256},[231,3819,3820],{"class":233,"line":381},[231,3821,3055],{"class":276},[231,3823,3824,3826,3828,3830,3832,3834,3836,3838,3840,3842,3844],{"class":233,"line":388},[231,3825,283],{"class":252},[231,3827,286],{"class":256},[231,3829,290],{"class":289},[231,3831,293],{"class":256},[231,3833,297],{"class":296},[231,3835,300],{"class":237},[231,3837,303],{"class":244},[231,3839,249],{"class":306},[231,3841,261],{"class":248},[231,3843,311],{"class":256},[231,3845,270],{"class":256},[231,3847,3848,3850,3852,3854,3856,3858,3860,3862,3864,3866,3868,3870,3872,3874,3876],{"class":233,"line":394},[231,3849,319],{"class":306},[231,3851,322],{"class":256},[231,3853,286],{"class":256},[231,3855,327],{"class":306},[231,3857,322],{"class":256},[231,3859,332],{"class":306},[231,3861,336],{"class":335},[231,3863,340],{"class":339},[231,3865,336],{"class":335},[231,3867,311],{"class":256},[231,3869,347],{"class":335},[231,3871,350],{"class":339},[231,3873,336],{"class":335},[231,3875,364],{"class":306},[231,3877,367],{"class":256},[231,3879,3880,3882,3884],{"class":233,"line":428},[231,3881,373],{"class":256},[231,3883,264],{"class":306},[231,3885,378],{"class":256},[231,3887,3888],{"class":233,"line":433},[231,3889,385],{"emptyLinePlaceholder":384},[231,3891,3892],{"class":233,"line":439},[231,3893,3894],{"class":276},"  \u002F\u002F 2. 驗證請求資料\n",[231,3896,3897,3899,3901,3903,3905,3907,3909,3911,3913,3915,3917,3919,3921],{"class":233,"line":462},[231,3898,283],{"class":252},[231,3900,399],{"class":289},[231,3902,297],{"class":296},[231,3904,300],{"class":237},[231,3906,406],{"class":244},[231,3908,249],{"class":306},[231,3910,261],{"class":248},[231,3912,311],{"class":256},[231,3914,415],{"class":248},[231,3916,418],{"class":256},[231,3918,421],{"class":248},[231,3920,264],{"class":306},[231,3922,378],{"class":256},[231,3924,3925],{"class":233,"line":492},[231,3926,385],{"emptyLinePlaceholder":384},[231,3928,3929],{"class":233,"line":497},[231,3930,436],{"class":276},[231,3932,3933,3935,3937,3939,3941,3943,3945,3947,3949],{"class":233,"line":503},[231,3934,283],{"class":252},[231,3936,444],{"class":289},[231,3938,297],{"class":296},[231,3940,300],{"class":237},[231,3942,451],{"class":244},[231,3944,249],{"class":306},[231,3946,261],{"class":248},[231,3948,264],{"class":306},[231,3950,378],{"class":256},[231,3952,3953,3955,3957,3959,3961,3963,3965,3967,3969,3971,3973,3975],{"class":233,"line":527},[231,3954,283],{"class":252},[231,3956,467],{"class":289},[231,3958,297],{"class":296},[231,3960,444],{"class":248},[231,3962,418],{"class":256},[231,3964,476],{"class":244},[231,3966,249],{"class":306},[231,3968,336],{"class":335},[231,3970,483],{"class":339},[231,3972,336],{"class":335},[231,3974,264],{"class":306},[231,3976,378],{"class":256},[231,3978,3979],{"class":233,"line":548},[231,3980,385],{"emptyLinePlaceholder":384},[231,3982,3983],{"class":233,"line":563},[231,3984,3985],{"class":276},"  \u002F\u002F 4. 新增資料\n",[231,3987,3988,3990,3992,3994,3996,3998,4000,4002,4004],{"class":233,"line":574},[231,3989,283],{"class":252},[231,3991,286],{"class":256},[231,3993,510],{"class":289},[231,3995,311],{"class":256},[231,3997,515],{"class":289},[231,3999,293],{"class":256},[231,4001,297],{"class":296},[231,4003,300],{"class":237},[231,4005,524],{"class":248},[231,4007,4008,4010,4012,4014,4016,4018,4020],{"class":233,"line":587},[231,4009,530],{"class":256},[231,4011,533],{"class":244},[231,4013,249],{"class":306},[231,4015,336],{"class":335},[231,4017,540],{"class":339},[231,4019,336],{"class":335},[231,4021,545],{"class":306},[231,4023,4024,4026,4028,4030,4032,4035,4037,4039,4042,4044,4046,4048,4050,4052],{"class":233,"line":592},[231,4025,530],{"class":256},[231,4027,553],{"class":244},[231,4029,249],{"class":306},[231,4031,626],{"class":256},[231,4033,4034],{"class":296}," ...",[231,4036,558],{"class":248},[231,4038,311],{"class":256},[231,4040,4041],{"class":306}," created_by",[231,4043,322],{"class":256},[231,4045,290],{"class":248},[231,4047,418],{"class":256},[231,4049,718],{"class":248},[231,4051,293],{"class":256},[231,4053,545],{"class":306},[231,4055,4056,4058,4060],{"class":233,"line":598},[231,4057,530],{"class":256},[231,4059,568],{"class":244},[231,4061,571],{"class":306},[231,4063,4064,4066,4068,4070],{"class":233,"line":615},[231,4065,530],{"class":256},[231,4067,579],{"class":244},[231,4069,582],{"class":306},[231,4071,378],{"class":256},[231,4073,4074],{"class":233,"line":658},[231,4075,385],{"emptyLinePlaceholder":384},[231,4077,4078],{"class":233,"line":664},[231,4079,595],{"class":276},[231,4081,4082,4084,4086,4088,4090,4092,4094,4096,4098,4100,4102],{"class":233,"line":669},[231,4083,601],{"class":237},[231,4085,257],{"class":306},[231,4087,606],{"class":248},[231,4089,2434],{"class":256},[231,4091,57],{"class":248},[231,4093,3399],{"class":296},[231,4095,347],{"class":335},[231,4097,2445],{"class":339},[231,4099,336],{"class":335},[231,4101,609],{"class":306},[231,4103,612],{"class":256},[231,4105,4106,4108,4110,4112,4114,4116,4118,4120,4122,4124,4126,4128,4131,4133,4135,4137],{"class":233,"line":675},[231,4107,618],{"class":237},[231,4109,621],{"class":244},[231,4111,249],{"class":306},[231,4113,626],{"class":256},[231,4115,629],{"class":306},[231,4117,322],{"class":256},[231,4119,2470],{"class":634},[231,4121,311],{"class":256},[231,4123,640],{"class":306},[231,4125,322],{"class":256},[231,4127,347],{"class":335},[231,4129,4130],{"class":339},"資料重複",[231,4132,336],{"class":335},[231,4134,293],{"class":256},[231,4136,264],{"class":306},[231,4138,378],{"class":256},[231,4140,4141],{"class":233,"line":706},[231,4142,661],{"class":256},[231,4144,4145,4147,4149,4151,4153],{"class":233,"line":724},[231,4146,601],{"class":237},[231,4148,257],{"class":306},[231,4150,606],{"class":248},[231,4152,609],{"class":306},[231,4154,612],{"class":256},[231,4156,4157,4159,4161,4163,4165,4167,4169,4171,4173,4175,4177,4179,4182,4184,4186,4188],{"class":233,"line":741},[231,4158,618],{"class":237},[231,4160,621],{"class":244},[231,4162,249],{"class":306},[231,4164,626],{"class":256},[231,4166,629],{"class":306},[231,4168,322],{"class":256},[231,4170,635],{"class":634},[231,4172,311],{"class":256},[231,4174,640],{"class":306},[231,4176,322],{"class":256},[231,4178,347],{"class":335},[231,4180,4181],{"class":339},"新增失敗",[231,4183,336],{"class":335},[231,4185,293],{"class":256},[231,4187,264],{"class":306},[231,4189,378],{"class":256},[231,4191,4192],{"class":233,"line":758},[231,4193,661],{"class":256},[231,4195,4196],{"class":233,"line":781},[231,4197,385],{"emptyLinePlaceholder":384},[231,4199,4200],{"class":233,"line":790},[231,4201,4202],{"class":276},"  \u002F\u002F 6. 記錄操作日誌\n",[231,4204,4205,4207,4209,4211,4213,4215,4217,4219,4221,4223,4225,4227,4229],{"class":233,"line":795},[231,4206,678],{"class":237},[231,4208,467],{"class":248},[231,4210,418],{"class":256},[231,4212,533],{"class":244},[231,4214,249],{"class":306},[231,4216,336],{"class":335},[231,4218,691],{"class":339},[231,4220,336],{"class":335},[231,4222,264],{"class":306},[231,4224,418],{"class":256},[231,4226,553],{"class":244},[231,4228,249],{"class":306},[231,4230,612],{"class":256},[231,4232,4233,4235,4237,4239,4241,4243],{"class":233,"line":801},[231,4234,709],{"class":306},[231,4236,322],{"class":256},[231,4238,290],{"class":248},[231,4240,418],{"class":256},[231,4242,718],{"class":248},[231,4244,721],{"class":256},[231,4246,4247,4249,4251,4253,4255,4257],{"class":233,"line":820},[231,4248,727],{"class":306},[231,4250,322],{"class":256},[231,4252,347],{"class":335},[231,4254,734],{"class":339},[231,4256,336],{"class":335},[231,4258,721],{"class":256},[231,4260,4261,4263,4265,4267,4269,4271],{"class":233,"line":833},[231,4262,744],{"class":306},[231,4264,322],{"class":256},[231,4266,347],{"class":335},[231,4268,751],{"class":339},[231,4270,336],{"class":335},[231,4272,721],{"class":256},[231,4274,4275,4277,4279,4281,4283,4285,4287,4289,4291],{"class":233,"line":3529},[231,4276,761],{"class":306},[231,4278,322],{"class":256},[231,4280,510],{"class":248},[231,4282,418],{"class":256},[231,4284,718],{"class":248},[231,4286,418],{"class":256},[231,4288,774],{"class":244},[231,4290,582],{"class":306},[231,4292,721],{"class":256},[231,4294,4295,4298,4300,4302],{"class":233,"line":3535},[231,4296,4297],{"class":306},"    details",[231,4299,322],{"class":256},[231,4301,399],{"class":248},[231,4303,721],{"class":256},[231,4305,4306,4308,4310],{"class":233,"line":3548},[231,4307,373],{"class":256},[231,4309,264],{"class":306},[231,4311,378],{"class":256},[231,4313,4314],{"class":233,"line":3584},[231,4315,385],{"emptyLinePlaceholder":384},[231,4317,4318],{"class":233,"line":3589},[231,4319,4320],{"class":276},"  \u002F\u002F 7. 回應\n",[231,4322,4323,4325,4327,4329,4331,4333,4335],{"class":233,"line":3594},[231,4324,804],{"class":244},[231,4326,249],{"class":306},[231,4328,261],{"class":248},[231,4330,311],{"class":256},[231,4332,813],{"class":634},[231,4334,264],{"class":306},[231,4336,378],{"class":256},[231,4338,4339,4341,4343,4345],{"class":233,"line":3600},[231,4340,823],{"class":237},[231,4342,286],{"class":256},[231,4344,510],{"class":248},[231,4346,830],{"class":256},[231,4348,4349,4351,4353],{"class":233,"line":3607},[231,4350,836],{"class":256},[231,4352,264],{"class":248},[231,4354,378],{"class":256},[45,4356,4358],{"id":4357},"patch-更新-api","PATCH 更新 API",[50,4360,4362],{"className":225,"code":4361,"language":227,"meta":59,"style":59},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fresources\u002F[id]\u002Findex.patch.ts\nimport { getSupabaseWithContext } from \"~~\u002Fserver\u002Futils\u002Fsupabase\";\nimport { updateResourceSchema, idParamSchema } from \"~~\u002Fshared\u002Ftypes\u002Fresources\";\n\nexport default defineEventHandler(async (event) => {\n  \u002F\u002F 1. 權限檢查\n  const { user } = await requireUserSession(event, {\n    user: { role: [\"admin\", \"manager\"] },\n  });\n\n  \u002F\u002F 2. 驗證參數與請求資料\n  const params = await getValidatedRouterParams(event, idParamSchema.parse);\n  const body = await readValidatedBody(event, updateResourceSchema.parse);\n\n  \u002F\u002F 3. 取得 Supabase Client\n  const supabase = await getSupabaseWithContext(event);\n  const db = supabase.schema(\"your_schema\");\n\n  \u002F\u002F 4. 更新資料\n  const { data, error } = await db\n    .from(\"resources\")\n    .update({ ...body, updated_at: new Date().toISOString() })\n    .eq(\"id\", params.id)\n    .is(\"deleted_at\", null)\n    .select()\n    .single();\n\n  \u002F\u002F 5. 錯誤處理\n  if (!data) {\n    throw createError({ statusCode: 404, message: \"找不到指定的資源\" });\n  }\n  if (error) {\n    throw createError({ statusCode: 500, message: \"更新失敗\" });\n  }\n\n  \u002F\u002F 6. 記錄操作日誌\n  await db.from(\"operation_logs\").insert({\n    user_id: user.id,\n    action: \"update\",\n    target_type: \"resource\",\n    target_id: params.id.toString(),\n    details: body,\n  });\n\n  \u002F\u002F 7. 回應\n  return { data };\n});\n",[57,4363,4364,4369,4389,4413,4417,4439,4443,4467,4499,4507,4511,4516,4544,4572,4576,4580,4600,4626,4630,4635,4655,4671,4713,4738,4758,4766,4776,4780,4784,4799,4833,4837,4849,4884,4888,4892,4896,4924,4938,4952,4966,4986,4996,5004,5008,5012,5022],{"__ignoreMap":59},[231,4365,4366],{"class":233,"line":234},[231,4367,4368],{"class":276},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fresources\u002F[id]\u002Findex.patch.ts\n",[231,4370,4371,4373,4375,4377,4379,4381,4383,4385,4387],{"class":233,"line":273},[231,4372,865],{"class":237},[231,4374,286],{"class":256},[231,4376,451],{"class":248},[231,4378,293],{"class":256},[231,4380,875],{"class":237},[231,4382,347],{"class":335},[231,4384,2999],{"class":339},[231,4386,336],{"class":335},[231,4388,378],{"class":256},[231,4390,4391,4393,4395,4397,4399,4401,4403,4405,4407,4409,4411],{"class":233,"line":280},[231,4392,865],{"class":237},[231,4394,286],{"class":256},[231,4396,1273],{"class":248},[231,4398,311],{"class":256},[231,4400,1304],{"class":248},[231,4402,293],{"class":256},[231,4404,875],{"class":237},[231,4406,347],{"class":335},[231,4408,3020],{"class":339},[231,4410,336],{"class":335},[231,4412,378],{"class":256},[231,4414,4415],{"class":233,"line":316},[231,4416,385],{"emptyLinePlaceholder":384},[231,4418,4419,4421,4423,4425,4427,4429,4431,4433,4435,4437],{"class":233,"line":370},[231,4420,238],{"class":237},[231,4422,241],{"class":237},[231,4424,245],{"class":244},[231,4426,249],{"class":248},[231,4428,253],{"class":252},[231,4430,257],{"class":256},[231,4432,261],{"class":260},[231,4434,264],{"class":256},[231,4436,267],{"class":252},[231,4438,270],{"class":256},[231,4440,4441],{"class":233,"line":381},[231,4442,3055],{"class":276},[231,4444,4445,4447,4449,4451,4453,4455,4457,4459,4461,4463,4465],{"class":233,"line":388},[231,4446,283],{"class":252},[231,4448,286],{"class":256},[231,4450,290],{"class":289},[231,4452,293],{"class":256},[231,4454,297],{"class":296},[231,4456,300],{"class":237},[231,4458,303],{"class":244},[231,4460,249],{"class":306},[231,4462,261],{"class":248},[231,4464,311],{"class":256},[231,4466,270],{"class":256},[231,4468,4469,4471,4473,4475,4477,4479,4481,4483,4485,4487,4489,4491,4493,4495,4497],{"class":233,"line":394},[231,4470,319],{"class":306},[231,4472,322],{"class":256},[231,4474,286],{"class":256},[231,4476,327],{"class":306},[231,4478,322],{"class":256},[231,4480,332],{"class":306},[231,4482,336],{"class":335},[231,4484,340],{"class":339},[231,4486,336],{"class":335},[231,4488,311],{"class":256},[231,4490,347],{"class":335},[231,4492,350],{"class":339},[231,4494,336],{"class":335},[231,4496,364],{"class":306},[231,4498,367],{"class":256},[231,4500,4501,4503,4505],{"class":233,"line":428},[231,4502,373],{"class":256},[231,4504,264],{"class":306},[231,4506,378],{"class":256},[231,4508,4509],{"class":233,"line":433},[231,4510,385],{"emptyLinePlaceholder":384},[231,4512,4513],{"class":233,"line":439},[231,4514,4515],{"class":276},"  \u002F\u002F 2. 驗證參數與請求資料\n",[231,4517,4518,4520,4522,4524,4526,4528,4530,4532,4534,4536,4538,4540,4542],{"class":233,"line":462},[231,4519,283],{"class":252},[231,4521,1449],{"class":289},[231,4523,297],{"class":296},[231,4525,300],{"class":237},[231,4527,1456],{"class":244},[231,4529,249],{"class":306},[231,4531,261],{"class":248},[231,4533,311],{"class":256},[231,4535,1304],{"class":248},[231,4537,418],{"class":256},[231,4539,421],{"class":248},[231,4541,264],{"class":306},[231,4543,378],{"class":256},[231,4545,4546,4548,4550,4552,4554,4556,4558,4560,4562,4564,4566,4568,4570],{"class":233,"line":492},[231,4547,283],{"class":252},[231,4549,399],{"class":289},[231,4551,297],{"class":296},[231,4553,300],{"class":237},[231,4555,406],{"class":244},[231,4557,249],{"class":306},[231,4559,261],{"class":248},[231,4561,311],{"class":256},[231,4563,1273],{"class":248},[231,4565,418],{"class":256},[231,4567,421],{"class":248},[231,4569,264],{"class":306},[231,4571,378],{"class":256},[231,4573,4574],{"class":233,"line":497},[231,4575,385],{"emptyLinePlaceholder":384},[231,4577,4578],{"class":233,"line":503},[231,4579,436],{"class":276},[231,4581,4582,4584,4586,4588,4590,4592,4594,4596,4598],{"class":233,"line":527},[231,4583,283],{"class":252},[231,4585,444],{"class":289},[231,4587,297],{"class":296},[231,4589,300],{"class":237},[231,4591,451],{"class":244},[231,4593,249],{"class":306},[231,4595,261],{"class":248},[231,4597,264],{"class":306},[231,4599,378],{"class":256},[231,4601,4602,4604,4606,4608,4610,4612,4614,4616,4618,4620,4622,4624],{"class":233,"line":548},[231,4603,283],{"class":252},[231,4605,467],{"class":289},[231,4607,297],{"class":296},[231,4609,444],{"class":248},[231,4611,418],{"class":256},[231,4613,476],{"class":244},[231,4615,249],{"class":306},[231,4617,336],{"class":335},[231,4619,483],{"class":339},[231,4621,336],{"class":335},[231,4623,264],{"class":306},[231,4625,378],{"class":256},[231,4627,4628],{"class":233,"line":563},[231,4629,385],{"emptyLinePlaceholder":384},[231,4631,4632],{"class":233,"line":574},[231,4633,4634],{"class":276},"  \u002F\u002F 4. 更新資料\n",[231,4636,4637,4639,4641,4643,4645,4647,4649,4651,4653],{"class":233,"line":587},[231,4638,283],{"class":252},[231,4640,286],{"class":256},[231,4642,510],{"class":289},[231,4644,311],{"class":256},[231,4646,515],{"class":289},[231,4648,293],{"class":256},[231,4650,297],{"class":296},[231,4652,300],{"class":237},[231,4654,524],{"class":248},[231,4656,4657,4659,4661,4663,4665,4667,4669],{"class":233,"line":592},[231,4658,530],{"class":256},[231,4660,533],{"class":244},[231,4662,249],{"class":306},[231,4664,336],{"class":335},[231,4666,540],{"class":339},[231,4668,336],{"class":335},[231,4670,545],{"class":306},[231,4672,4673,4675,4678,4680,4682,4684,4686,4688,4691,4693,4696,4699,4701,4703,4706,4709,4711],{"class":233,"line":598},[231,4674,530],{"class":256},[231,4676,4677],{"class":244},"update",[231,4679,249],{"class":306},[231,4681,626],{"class":256},[231,4683,4034],{"class":296},[231,4685,558],{"class":248},[231,4687,311],{"class":256},[231,4689,4690],{"class":306}," updated_at",[231,4692,322],{"class":256},[231,4694,4695],{"class":296}," new",[231,4697,4698],{"class":244}," Date",[231,4700,582],{"class":306},[231,4702,418],{"class":256},[231,4704,4705],{"class":244},"toISOString",[231,4707,4708],{"class":306},"() ",[231,4710,836],{"class":256},[231,4712,545],{"class":306},[231,4714,4715,4717,4720,4722,4724,4726,4728,4730,4732,4734,4736],{"class":233,"line":615},[231,4716,530],{"class":256},[231,4718,4719],{"class":244},"eq",[231,4721,249],{"class":306},[231,4723,336],{"class":335},[231,4725,718],{"class":339},[231,4727,336],{"class":335},[231,4729,311],{"class":256},[231,4731,1449],{"class":248},[231,4733,418],{"class":256},[231,4735,718],{"class":248},[231,4737,545],{"class":306},[231,4739,4740,4742,4744,4746,4748,4750,4752,4754,4756],{"class":233,"line":658},[231,4741,530],{"class":256},[231,4743,3235],{"class":244},[231,4745,249],{"class":306},[231,4747,336],{"class":335},[231,4749,3242],{"class":339},[231,4751,336],{"class":335},[231,4753,311],{"class":256},[231,4755,2167],{"class":1497},[231,4757,545],{"class":306},[231,4759,4760,4762,4764],{"class":233,"line":664},[231,4761,530],{"class":256},[231,4763,568],{"class":244},[231,4765,571],{"class":306},[231,4767,4768,4770,4772,4774],{"class":233,"line":669},[231,4769,530],{"class":256},[231,4771,579],{"class":244},[231,4773,582],{"class":306},[231,4775,378],{"class":256},[231,4777,4778],{"class":233,"line":675},[231,4779,385],{"emptyLinePlaceholder":384},[231,4781,4782],{"class":233,"line":706},[231,4783,595],{"class":276},[231,4785,4786,4788,4790,4792,4795,4797],{"class":233,"line":724},[231,4787,601],{"class":237},[231,4789,257],{"class":306},[231,4791,2359],{"class":296},[231,4793,4794],{"class":248},"data",[231,4796,609],{"class":306},[231,4798,612],{"class":256},[231,4800,4801,4803,4805,4807,4809,4811,4813,4815,4817,4819,4821,4823,4825,4827,4829,4831],{"class":233,"line":741},[231,4802,618],{"class":237},[231,4804,621],{"class":244},[231,4806,249],{"class":306},[231,4808,626],{"class":256},[231,4810,629],{"class":306},[231,4812,322],{"class":256},[231,4814,2385],{"class":634},[231,4816,311],{"class":256},[231,4818,640],{"class":306},[231,4820,322],{"class":256},[231,4822,347],{"class":335},[231,4824,2399],{"class":339},[231,4826,336],{"class":335},[231,4828,293],{"class":256},[231,4830,264],{"class":306},[231,4832,378],{"class":256},[231,4834,4835],{"class":233,"line":758},[231,4836,661],{"class":256},[231,4838,4839,4841,4843,4845,4847],{"class":233,"line":781},[231,4840,601],{"class":237},[231,4842,257],{"class":306},[231,4844,606],{"class":248},[231,4846,609],{"class":306},[231,4848,612],{"class":256},[231,4850,4851,4853,4855,4857,4859,4861,4863,4865,4867,4869,4871,4873,4876,4878,4880,4882],{"class":233,"line":790},[231,4852,618],{"class":237},[231,4854,621],{"class":244},[231,4856,249],{"class":306},[231,4858,626],{"class":256},[231,4860,629],{"class":306},[231,4862,322],{"class":256},[231,4864,635],{"class":634},[231,4866,311],{"class":256},[231,4868,640],{"class":306},[231,4870,322],{"class":256},[231,4872,347],{"class":335},[231,4874,4875],{"class":339},"更新失敗",[231,4877,336],{"class":335},[231,4879,293],{"class":256},[231,4881,264],{"class":306},[231,4883,378],{"class":256},[231,4885,4886],{"class":233,"line":795},[231,4887,661],{"class":256},[231,4889,4890],{"class":233,"line":801},[231,4891,385],{"emptyLinePlaceholder":384},[231,4893,4894],{"class":233,"line":820},[231,4895,4202],{"class":276},[231,4897,4898,4900,4902,4904,4906,4908,4910,4912,4914,4916,4918,4920,4922],{"class":233,"line":833},[231,4899,678],{"class":237},[231,4901,467],{"class":248},[231,4903,418],{"class":256},[231,4905,533],{"class":244},[231,4907,249],{"class":306},[231,4909,336],{"class":335},[231,4911,691],{"class":339},[231,4913,336],{"class":335},[231,4915,264],{"class":306},[231,4917,418],{"class":256},[231,4919,553],{"class":244},[231,4921,249],{"class":306},[231,4923,612],{"class":256},[231,4925,4926,4928,4930,4932,4934,4936],{"class":233,"line":3529},[231,4927,709],{"class":306},[231,4929,322],{"class":256},[231,4931,290],{"class":248},[231,4933,418],{"class":256},[231,4935,718],{"class":248},[231,4937,721],{"class":256},[231,4939,4940,4942,4944,4946,4948,4950],{"class":233,"line":3535},[231,4941,727],{"class":306},[231,4943,322],{"class":256},[231,4945,347],{"class":335},[231,4947,4677],{"class":339},[231,4949,336],{"class":335},[231,4951,721],{"class":256},[231,4953,4954,4956,4958,4960,4962,4964],{"class":233,"line":3548},[231,4955,744],{"class":306},[231,4957,322],{"class":256},[231,4959,347],{"class":335},[231,4961,751],{"class":339},[231,4963,336],{"class":335},[231,4965,721],{"class":256},[231,4967,4968,4970,4972,4974,4976,4978,4980,4982,4984],{"class":233,"line":3584},[231,4969,761],{"class":306},[231,4971,322],{"class":256},[231,4973,1449],{"class":248},[231,4975,418],{"class":256},[231,4977,718],{"class":248},[231,4979,418],{"class":256},[231,4981,774],{"class":244},[231,4983,582],{"class":306},[231,4985,721],{"class":256},[231,4987,4988,4990,4992,4994],{"class":233,"line":3589},[231,4989,4297],{"class":306},[231,4991,322],{"class":256},[231,4993,399],{"class":248},[231,4995,721],{"class":256},[231,4997,4998,5000,5002],{"class":233,"line":3594},[231,4999,373],{"class":256},[231,5001,264],{"class":306},[231,5003,378],{"class":256},[231,5005,5006],{"class":233,"line":3600},[231,5007,385],{"emptyLinePlaceholder":384},[231,5009,5010],{"class":233,"line":3607},[231,5011,4320],{"class":276},[231,5013,5014,5016,5018,5020],{"class":233,"line":3624},[231,5015,823],{"class":237},[231,5017,286],{"class":256},[231,5019,510],{"class":248},[231,5021,830],{"class":256},[231,5023,5024,5026,5028],{"class":233,"line":3634},[231,5025,836],{"class":256},[231,5027,264],{"class":248},[231,5029,378],{"class":256},[38,5031],{},[11,5033,5034],{"id":5034},"踩坑經驗",[45,5036,5037],{"id":5037},"路由衝突的陷阱",[15,5039,5040,5043],{},[183,5041,5042],{},"問題","：API 回傳 HTML 而非 JSON。",[15,5045,5046,5049,5050,190,5052,5054],{},[183,5047,5048],{},"原因","：同一目錄下同時有 ",[57,5051,189],{},[57,5053,193],{},"。",[50,5056,5059],{"className":5057,"code":5058,"language":55},[53],"❌ 錯誤結構\nserver\u002Fapi\u002Fv1\u002Fusers\u002F\n├── [id].ts         # 處理所有方法\n└── [id]\u002F\n    └── roles.ts    # 子路由\n\n✅ 正確結構\nserver\u002Fapi\u002Fv1\u002Fusers\u002F\n└── [id]\u002F\n    ├── index.get.ts\n    ├── index.patch.ts\n    ├── index.delete.ts\n    └── roles.get.ts\n",[57,5060,5058],{"__ignoreMap":59},[45,5062,5064],{"id":5063},"query-參數類型轉換","Query 參數類型轉換",[15,5066,5067,5069,5070,5072],{},[183,5068,5042],{},"：",[57,5071,1931],{}," 參數應該是數字，但 Query 參數預設都是字串。",[15,5074,5075,5078,5079,5082],{},[183,5076,5077],{},"解決","：使用 ",[57,5080,5081],{},"z.coerce.number()"," 自動轉換。",[50,5084,5086],{"className":225,"code":5085,"language":227,"meta":59,"style":59},"\u002F\u002F ❌ 錯誤：不會自動轉換\nz.number(); \u002F\u002F '1' 會驗證失敗\n\n\u002F\u002F ✅ 正確：自動將字串轉為數字\nz.coerce.number().int().positive(); \u002F\u002F '1' → 1\n",[57,5087,5088,5093,5109,5113,5118],{"__ignoreMap":59},[231,5089,5090],{"class":233,"line":234},[231,5091,5092],{"class":276},"\u002F\u002F ❌ 錯誤：不會自動轉換\n",[231,5094,5095,5098,5100,5102,5104,5106],{"class":233,"line":273},[231,5096,5097],{"class":248},"z",[231,5099,418],{"class":256},[231,5101,935],{"class":244},[231,5103,582],{"class":248},[231,5105,1801],{"class":256},[231,5107,5108],{"class":276}," \u002F\u002F '1' 會驗證失敗\n",[231,5110,5111],{"class":233,"line":280},[231,5112,385],{"emptyLinePlaceholder":384},[231,5114,5115],{"class":233,"line":316},[231,5116,5117],{"class":276},"\u002F\u002F ✅ 正確：自動將字串轉為數字\n",[231,5119,5120,5122,5124,5126,5128,5130,5132,5134,5136,5138,5140,5142,5144,5146],{"class":233,"line":370},[231,5121,5097],{"class":248},[231,5123,418],{"class":256},[231,5125,930],{"class":248},[231,5127,418],{"class":256},[231,5129,935],{"class":244},[231,5131,582],{"class":248},[231,5133,418],{"class":256},[231,5135,942],{"class":244},[231,5137,582],{"class":248},[231,5139,418],{"class":256},[231,5141,949],{"class":244},[231,5143,582],{"class":248},[231,5145,1801],{"class":256},[231,5147,5148],{"class":276}," \u002F\u002F '1' → 1\n",[45,5150,5151],{"id":5151},"忘記過濾軟刪除資料",[15,5153,5154,5156],{},[183,5155,5042],{},"：列表顯示已刪除的資料。",[15,5158,5159,5161,5162,5054],{},[183,5160,5077],{},"：所有查詢都加上 ",[57,5163,5164],{},".is('deleted_at', null)",[50,5166,5168],{"className":225,"code":5167,"language":227,"meta":59,"style":59},"\u002F\u002F ✅ 正確：排除已軟刪除的資料\nconst { data } = await db.from(\"resources\").select(\"*\").is(\"deleted_at\", null); \u002F\u002F 必須加這行\n",[57,5169,5170,5175],{"__ignoreMap":59},[231,5171,5172],{"class":233,"line":234},[231,5173,5174],{"class":276},"\u002F\u002F ✅ 正確：排除已軟刪除的資料\n",[231,5176,5177,5179,5181,5183,5185,5187,5189,5191,5193,5195,5197,5199,5201,5203,5205,5207,5209,5211,5213,5215,5217,5219,5221,5223,5225,5227,5229,5231,5233,5235,5237,5239],{"class":233,"line":273},[231,5178,1376],{"class":252},[231,5180,286],{"class":256},[231,5182,510],{"class":289},[231,5184,293],{"class":256},[231,5186,297],{"class":296},[231,5188,300],{"class":237},[231,5190,467],{"class":248},[231,5192,418],{"class":256},[231,5194,533],{"class":244},[231,5196,249],{"class":248},[231,5198,336],{"class":335},[231,5200,540],{"class":339},[231,5202,336],{"class":335},[231,5204,264],{"class":248},[231,5206,418],{"class":256},[231,5208,568],{"class":244},[231,5210,249],{"class":248},[231,5212,336],{"class":335},[231,5214,2840],{"class":339},[231,5216,336],{"class":335},[231,5218,264],{"class":248},[231,5220,418],{"class":256},[231,5222,3235],{"class":244},[231,5224,249],{"class":248},[231,5226,336],{"class":335},[231,5228,3242],{"class":339},[231,5230,336],{"class":335},[231,5232,311],{"class":256},[231,5234,2167],{"class":1497},[231,5236,264],{"class":248},[231,5238,1801],{"class":256},[231,5240,5241],{"class":276}," \u002F\u002F 必須加這行\n",[38,5243],{},[11,5245,5246],{"id":5246},"檢查清單",[15,5248,5249],{},"建立新 API 時，確認以下項目：",[19,5251,5254,5266,5276,5285,5297,5305,5311,5320,5326],{"className":5252},[5253],"contains-task-list",[22,5255,5258,5262,5263,5265],{"className":5256},[5257],"task-list-item",[5259,5260],"input",{"disabled":384,"type":5261},"checkbox"," 使用正確的目錄結構（",[57,5264,163],{},"）",[22,5267,5269,5271,5272,5275],{"className":5268},[5257],[5259,5270],{"disabled":384,"type":5261}," 在 ",[57,5273,5274],{},"shared\u002Ftypes\u002F"," 定義 Zod Schema",[22,5277,5279,5281,5282,5265],{"className":5278},[5257],[5259,5280],{"disabled":384,"type":5261}," 在最開頭進行權限檢查（",[57,5283,5284],{},"requireUserSession",[22,5286,5288,5290,5291,5293,5294,5296],{"className":5287},[5257],[5259,5289],{"disabled":384,"type":5261}," 使用 ",[57,5292,1477],{}," 或 ",[57,5295,1480],{}," 驗證輸入",[22,5298,5300,5302,5303,5265],{"className":5299},[5257],[5259,5301],{"disabled":384,"type":5261}," 查詢時過濾軟刪除（",[57,5304,5164],{},[22,5306,5308,5310],{"className":5307},[5257],[5259,5309],{"disabled":384,"type":5261}," 正確處理錯誤（404、409、500）",[22,5312,5314,5316,5317,5265],{"className":5313},[5257],[5259,5315],{"disabled":384,"type":5261}," 回傳統一格式（",[57,5318,5319],{},"{ data, pagination? }",[22,5321,5323,5325],{"className":5322},[5257],[5259,5324],{"disabled":384,"type":5261}," 新增操作設定 201 狀態碼",[22,5327,5329,5331],{"className":5328},[5257],[5259,5330],{"disabled":384,"type":5261}," 異動操作記錄操作日誌",[38,5333],{},[11,5335,5336],{"id":5336},"最佳實踐總結",[178,5338,5339,5347,5355,5363,5371,5384,5390],{},[22,5340,5341,5078,5343,5346],{},[183,5342,48],{},[57,5344,5345],{},"\u002Fapi\u002Fv1\u002F"," 前綴",[22,5348,5349,5078,5352,5354],{},[183,5350,5351],{},"目錄結構",[57,5353,163],{}," 避免路由衝突",[22,5356,5357,5360,5361],{},[183,5358,5359],{},"Zod 驗證","：所有輸入都要驗證，Schema 放 ",[57,5362,5274],{},[22,5364,5365,5069,5368,5370],{},[183,5366,5367],{},"權限優先",[57,5369,5284],{}," 放在最前面",[22,5372,5373,5376,5377,5380,5381],{},[183,5374,5375],{},"統一回應","：列表用 ",[57,5378,5379],{},"{ data, pagination }","，單筆用 ",[57,5382,5383],{},"{ data }",[22,5385,5386,5389],{},[183,5387,5388],{},"語意化錯誤","：使用適當的 HTTP 狀態碼",[22,5391,5392,5395],{},[183,5393,5394],{},"操作日誌","：所有 CUD 操作都要記錄",[38,5397],{},[11,5399,5400],{"id":5400},"延伸閱讀",[19,5402,5403,5412,5419,5426],{},[22,5404,5405],{},[5406,5407,5411],"a",{"href":5408,"rel":5409},"https:\u002F\u002Fnitro.unjs.io\u002F",[5410],"nofollow","Nitro 文件",[22,5413,5414],{},[5406,5415,5418],{"href":5416,"rel":5417},"https:\u002F\u002Fh3.unjs.io\u002Futils\u002Frequest#validating-with-validation-libraries",[5410],"h3 驗證 API",[22,5420,5421,5422],{},"上一篇：",[5406,5423,5425],{"href":5424},"\u002Fblog\u002Fnuxt\u002Fsupabase-self-hosted","Self-hosted Supabase 部署與遷移",[22,5427,5428,5429],{},"下一篇：",[5406,5430,5432],{"href":5431},"\u002Fblog\u002Fnuxt\u002Fpinia-colada-async-state","Pinia Colada 非同步狀態管理",[5434,5435,5436],"style",{},"html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sseR_, html code.shiki .sseR_{--shiki-light:#9C3EDA;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":59,"searchDepth":280,"depth":280,"links":5438},[5439,5440,5446,5449,5454,5458,5463,5467,5472,5477,5482,5483,5484],{"id":13,"depth":273,"text":13},{"id":42,"depth":273,"text":43,"children":5441},[5442,5443,5444],{"id":47,"depth":280,"text":48},{"id":62,"depth":280,"text":62},{"id":159,"depth":280,"text":5445},"為什麼使用 [id]\u002Findex.*.ts 而非 [id].*.ts",{"id":214,"depth":273,"text":215,"children":5447},[5448],{"id":218,"depth":280,"text":219},{"id":845,"depth":273,"text":846,"children":5450},[5451,5452,5453],{"id":849,"depth":280,"text":850},{"id":1360,"depth":280,"text":1361},{"id":1471,"depth":280,"text":1471},{"id":1559,"depth":273,"text":1559,"children":5455},[5456,5457],{"id":1562,"depth":280,"text":1563},{"id":1703,"depth":280,"text":1703},{"id":1774,"depth":273,"text":1774,"children":5459},[5460,5461,5462],{"id":1777,"depth":280,"text":1778},{"id":2012,"depth":280,"text":2012},{"id":2117,"depth":280,"text":2117},{"id":2201,"depth":273,"text":2201,"children":5464},[5465,5466],{"id":2204,"depth":280,"text":2204},{"id":2268,"depth":280,"text":2269},{"id":2595,"depth":273,"text":2595,"children":5468},[5469,5470,5471],{"id":2598,"depth":280,"text":2598},{"id":2716,"depth":280,"text":2716},{"id":2804,"depth":280,"text":2804},{"id":2967,"depth":273,"text":2968,"children":5473},[5474,5475,5476],{"id":2971,"depth":280,"text":2972},{"id":3740,"depth":280,"text":3741},{"id":4357,"depth":280,"text":4358},{"id":5034,"depth":273,"text":5034,"children":5478},[5479,5480,5481],{"id":5037,"depth":280,"text":5037},{"id":5063,"depth":280,"text":5064},{"id":5151,"depth":280,"text":5151},{"id":5246,"depth":273,"text":5246},{"id":5336,"depth":273,"text":5336},{"id":5400,"depth":273,"text":5400},"Nuxt","2026-01-22","使用 Nitro 建構 RESTful API，涵蓋目錄結構、Zod 驗證、統一回應格式與錯誤處理。",false,"md",null,{},"\u002Fblog\u002Fnuxt\u002Fnitro-api-design",{"title":5,"description":5487},"nuxt-fullstack","Nuxt 4 全棧實戰筆記","blog\u002Fnuxt\u002Fnitro-api-design\u002Findex",[5485,5498,5499],"Nitro","Pinia","CFDk-xsVTAUOAsWM6taXlfoZAdfzD7vLoArRXJ-AOOc",1780512499430]