{"openapi":"3.1.0","info":{"title":"噪音監測資料上傳API","description":"接收科技執法噪音監測 JSON 與 Base64 照片（自動轉為標準 JPEG 存入 Google Cloud Storage），並支援 GCS V4 Signed URL 與 Multipart 串流影片上傳及監測資料查詢服務。","version":"1.0.0"},"paths":{"/api/v1/auth/login":{"post":{"tags":["Auth & Users"],"summary":"Login","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["Auth & Users"],"summary":"Get Me","operationId":"get_me_api_v1_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/change-password":{"post":{"tags":["Auth & Users"],"summary":"Change Own Password","description":"已登入之使用者（含管理員或一般設備商帳號）自行修改密碼","operationId":"change_own_password_api_v1_auth_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/{user_id}/reset-password":{"post":{"tags":["Auth & Users"],"summary":"Admin Reset User Password","description":"系統管理員直接重設指定使用者（或管理員帳號）之密碼","operationId":"admin_reset_user_password_api_v1_users__user_id__reset_password_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminResetPasswordRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users":{"get":{"tags":["Auth & Users"],"summary":"List Users","operationId":"list_users_api_v1_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UserResponse"},"type":"array","title":"Response List Users Api V1 Users Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Auth & Users"],"summary":"Create User","description":"新增使用者並強制填寫「設備所屬組織 (organization)」以釐清資料來源","operationId":"create_user_api_v1_users_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/{user_id}/unlock":{"post":{"tags":["Auth & Users"],"summary":"Unlock User Account","description":"管理員解除因連續登入失敗或人工停用之使用者帳號鎖定","operationId":"unlock_user_account_api_v1_users__user_id__unlock_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{user_id}/disable":{"post":{"tags":["Auth & Users"],"summary":"Disable User Account","operationId":"disable_user_account_api_v1_users__user_id__disable_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"reason","in":"query","required":false,"schema":{"type":"string","description":"停用原因","default":"管理員手動停用帳號","title":"Reason"},"description":"停用原因"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tokens":{"get":{"tags":["API Tokens"],"summary":"List Api Tokens","operationId":"list_api_tokens_api_v1_tokens_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TokenInfoResponse"},"type":"array","title":"Response List Api Tokens Api V1 Tokens Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["API Tokens"],"summary":"Create Api Token","description":"簽發新的設備上傳 API Token：\n- 可自訂有效天數 (`expires_in_days`) 或指定精確到期時間 (`expires_at`)\n- 自動綁定使用者所屬組織 (`organization`) 以追蹤上傳資料來源","operationId":"create_api_token_api_v1_tokens_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenCreatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/tokens/{token_id}/revoke":{"post":{"tags":["API Tokens"],"summary":"Revoke Api Token","operationId":"revoke_api_token_api_v1_tokens__token_id__revoke_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"token_id","in":"path","required":true,"schema":{"type":"integer","title":"Token Id"}},{"name":"reason","in":"query","required":false,"schema":{"type":"string","description":"停用原因","default":"管理員手動停用 Token","title":"Reason"},"description":"停用原因"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenInfoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tokens/{token_id}/reactivate":{"post":{"tags":["API Tokens"],"summary":"Reactivate Api Token","description":"管理員重新啟用未過期之 API Token","operationId":"reactivate_api_token_api_v1_tokens__token_id__reactivate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"token_id","in":"path","required":true,"schema":{"type":"integer","title":"Token Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenInfoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/noise/upload":{"post":{"tags":["Noise Data & GCS Upload"],"summary":"Upload Noise Data","description":"接收設備上傳之噪音監測 JSON 資料：\n1. 支援範例 JSON 全欄位（含容錯處理全形冒號 `：`）。\n2. 將 Base64 編碼之「槍機照片」、「車牌照片」、「魚眼照片」轉換為最佳化 **JPEG (`image/jpeg`)** 格式，上傳至 Google Cloud Storage。\n3. 自動更新 JSON 內對應照片之 GCS 路徑並移除 Base64 字串，將 JSON 存入 Google Cloud Storage 供後續噪音網站快速查詢與展示。","operationId":"upload_noise_data_api_v1_noise_upload_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/noise/video/signed-url":{"post":{"tags":["Video Upload"],"summary":"Create Video Signed Url","description":"【推薦方案 A】簽發 Google Cloud Storage V4 Signed URL (預簽章網址)：\n讓設備端直接以 HTTP PUT 二進位串流將 MP4 影片直傳至 GCS，\n避免將數十 MB 影片轉成 Base64 塞入 JSON 造成體積膨脹 33% 與 API 伺服器記憶體滿載。","operationId":"create_video_signed_url_api_v1_noise_video_signed_url_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoSignedUrlRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/noise/video/upload":{"post":{"tags":["Video Upload"],"summary":"Upload Video Multipart","description":"【推薦方案 B】Multipart 二進位串流影片上傳端點：\n- 驗證表單欄位與影片檔案格式。\n- 以 1MB Chunk 串流寫入 Google Cloud Storage，並自動更新對應噪音紀錄之影片路徑。","operationId":"upload_video_multipart_api_v1_noise_video_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_video_multipart_api_v1_noise_video_upload_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/noise/records":{"get":{"tags":["Noise Data & GCS Upload"],"summary":"List Noise Records","operationId":"list_noise_records_api_v1_noise_records_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"point_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"依點位編號篩選 (如 A118)","title":"Point Id"},"description":"依點位編號篩選 (如 A118)"},{"name":"plate_number","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"依車牌號碼模糊篩選","title":"Plate Number"},"description":"依車牌號碼模糊篩選"},{"name":"organization","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"依設備所屬組織篩選","title":"Organization"},"description":"依設備所屬組織篩選"},{"name":"exceed_only","in":"query","required":false,"schema":{"type":"boolean","description":"僅顯示超標案件 (噪音超標 > 0)","default":false,"title":"Exceed Only"},"description":"僅顯示超標案件 (噪音超標 > 0)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AdminResetPasswordRequest":{"properties":{"new_password":{"type":"string","maxLength":128,"minLength":8,"title":"New Password","description":"管理員為該帳號設定之新密碼 (至少 8 個字元)"}},"type":"object","required":["new_password"],"title":"AdminResetPasswordRequest"},"Body_upload_video_multipart_api_v1_noise_video_upload_post":{"properties":{"point_id":{"type":"string","title":"Point Id","description":"點位編號 (如 A118)"},"plate_number":{"type":"string","title":"Plate Number","description":"車牌辨識 (如 AMA-5237)"},"measured_time":{"type":"string","title":"Measured Time","description":"量測時間 (如 2026-11-04 12:05:45 727)"},"camera_type":{"type":"string","title":"Camera Type","description":"鏡頭類型：normal (機槍影片) 或 wide (魚眼影片)","default":"normal"},"record_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Record Id","description":"選填：關聯之噪音紀錄 ID"},"video_file":{"type":"string","contentMediaType":"application/octet-stream","title":"Video File","description":"MP4 影片二進位檔案"}},"type":"object","required":["point_id","plate_number","measured_time","video_file"],"title":"Body_upload_video_multipart_api_v1_noise_video_upload_post"},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","title":"Current Password","description":"目前使用中的舊密碼"},"new_password":{"type":"string","maxLength":128,"minLength":8,"title":"New Password","description":"新密碼 (至少 8 個字元)"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LoginRequest":{"properties":{"username":{"type":"string","title":"Username","description":"使用者帳號"},"password":{"type":"string","title":"Password","description":"使用者密碼"}},"type":"object","required":["username","password"],"title":"LoginRequest"},"TokenCreateRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Token 名稱或設備編號說明 (如：A118固定式噪音設備)"},"point_id":{"type":"string","title":"Point Id","description":"綁定點位編號 (如 A118 或 *)","default":"*"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":3650.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Token 有效天數 (若同時指定 expires_at 則以 expires_at 為主)","default":30},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"指定到期時間 (ISO-8601 格式，選填)"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id","description":"管理員可指定為哪位使用者/組織建立 Token；一般使用者則綁定自己"}},"type":"object","required":["name"],"title":"TokenCreateRequest"},"TokenCreatedResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"organization":{"type":"string","title":"Organization"},"point_id":{"type":"string","title":"Point Id"},"token_prefix":{"type":"string","title":"Token Prefix"},"raw_token":{"type":"string","title":"Raw Token","description":"明文 API Token (僅於建立時顯示一次，請妥善保存)"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","organization","point_id","token_prefix","raw_token","expires_at","created_at"],"title":"TokenCreatedResponse"},"TokenInfoResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"name":{"type":"string","title":"Name"},"organization":{"type":"string","title":"Organization"},"point_id":{"type":"string","title":"Point Id"},"token_prefix":{"type":"string","title":"Token Prefix"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"is_active":{"type":"boolean","title":"Is Active"},"revoked_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked Reason"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"last_used_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used Ip"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","user_id","name","organization","point_id","token_prefix","expires_at","is_active","revoked_reason","revoked_at","last_used_at","last_used_ip","created_at"],"title":"TokenInfoResponse"},"UserCreateRequest":{"properties":{"username":{"type":"string","maxLength":64,"minLength":3,"title":"Username","description":"使用者帳號"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password","description":"使用者密碼"},"full_name":{"type":"string","title":"Full Name","description":"聯絡人或設備管理員姓名"},"organization":{"type":"string","title":"Organization","description":"設備所屬組織/機關/廠商名稱（用於釐清資料來源）"},"org_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Code","description":"組織統編或單位代碼"},"allowed_points":{"type":"string","title":"Allowed Points","description":"允許上傳之點位編號(逗號分隔，如 A118,A119 或 * 代表不限)","default":"*"},"role":{"type":"string","title":"Role","description":"角色: admin / uploader / auditor","default":"uploader"}},"type":"object","required":["username","password","full_name","organization"],"title":"UserCreateRequest"},"UserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"username":{"type":"string","title":"Username"},"full_name":{"type":"string","title":"Full Name"},"organization":{"type":"string","title":"Organization"},"org_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Code"},"allowed_points":{"type":"string","title":"Allowed Points"},"role":{"type":"string","title":"Role"},"is_active":{"type":"boolean","title":"Is Active"},"locked_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locked Reason"},"locked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Locked At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","username","full_name","organization","org_code","allowed_points","role","is_active","locked_reason","locked_at","created_at"],"title":"UserResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VideoSignedUrlRequest":{"properties":{"record_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Record Id","description":"已上傳之噪音紀錄 ID (若有則自動關聯)"},"point_id":{"type":"string","title":"Point Id","description":"點位編號 (如 A118)"},"plate_number":{"type":"string","title":"Plate Number","description":"車牌號碼 (如 AMA-5237)"},"measured_time":{"type":"string","title":"Measured Time","description":"量測時間 (如 2026-11-04 12:05:45 727)"},"camera_type":{"type":"string","title":"Camera Type","description":"鏡頭類型：normal (機槍影片) 或 wide (魚眼影片)","default":"normal"},"content_type":{"type":"string","title":"Content Type","description":"影片 MIME 類型 (預設 video/mp4)","default":"video/mp4"},"file_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size Bytes","description":"預計上傳之影片檔案大小 (Bytes)"},"sha256_checksum":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256 Checksum","description":"影片檔案 SHA-256 雜湊值 (供完整性驗證)"}},"type":"object","required":["point_id","plate_number","measured_time"],"title":"VideoSignedUrlRequest"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}