11from fastapi import APIRouter , Depends , HTTPException
22from sqlalchemy .orm import Session
33from pydantic import BaseModel
4- from typing import Optional
4+ from typing import Optional , List
55
6- # --- IMPORT RATE LIMITER ---
6+ # --- IMPORT RATE LIMITER (Anti-Spam) ---
77from fastapi_limiter .depends import RateLimiter
88
9- # --- IMPORT AUTH (FIX PATH ) ---
10- from app .api .v1 .auth import get_current_user # <-- Path harus 'app.auth'
9+ # --- IMPORT AUTH (Satpam Firebase ) ---
10+ from app .api .v1 .auth import get_current_user
1111
12- # --- IMPORT DATABASE (FIX PATH) ---
13- from app .core .database import get_db # <-- Path harus 'app.db.database'
12+ # --- IMPORT DATABASE ---
13+ from app .core .database import get_db
1414from app .models .device import Device
1515
16- # --- IMPORT MQTT ---
16+ # --- IMPORT MQTT (Buat Control) ---
1717from app .mqtt .client import mqtt_client
1818
1919router = APIRouter ()
2020
21- # --- SCHEMA ---
21+ # ==========================================
22+ # 1. SCHEMAS
23+ # ==========================================
2224class UserClaimSchema (BaseModel ):
2325 device_id : str
2426 pin_code : str
@@ -28,12 +30,42 @@ class AutoRegisterSchema(BaseModel):
2830 pin_code : str
2931 factory_secret : str
3032
31- # --- 1. ENDPOINT CLAIM (User) ---
33+ class UpdateDeviceSchema (BaseModel ):
34+ device_name : str
35+
36+ class DeviceResponse (BaseModel ):
37+ device_id : str
38+ device_name : str
39+ owner_uid : Optional [str ] = None
40+
41+ class Config :
42+ from_attributes = True
43+
44+ # ==========================================
45+ # 2. ENDPOINTS
46+ # ==========================================
47+
48+ # --- A. LIST MY DEVICES (Dashboard) ---
49+ # Limit: 20 request / menit (Karena sering direfresh user)
50+ @router .get ("/my-devices" , response_model = List [DeviceResponse ], dependencies = [Depends (RateLimiter (times = 20 , seconds = 60 ))])
51+ def get_my_devices (
52+ db : Session = Depends (get_db ),
53+ user_uid : str = Depends (get_current_user ) # 🔒 Wajib Login
54+ ):
55+ """
56+ Mengambil daftar alat milik user yang sedang login.
57+ """
58+ devices = db .query (Device ).filter (Device .owner_uid == user_uid ).all ()
59+ return devices
60+
61+
62+ # --- B. CLAIM DEVICE ---
63+ # Limit: 5 request / menit (Biar gak brute-force PIN)
3264@router .post ("/claim" , dependencies = [Depends (RateLimiter (times = 5 , seconds = 60 ))])
3365def claim_device (
3466 claim_data : UserClaimSchema ,
3567 db : Session = Depends (get_db ),
36- user_uid : str = Depends (get_current_user ) # <-- Wajib Login
68+ user_uid : str = Depends (get_current_user ) # 🔒 Wajib Login
3769):
3870 clean_id = claim_data .device_id .strip ().upper ()
3971 clean_pin = claim_data .pin_code .strip ()
@@ -43,7 +75,6 @@ def claim_device(
4375 if not device :
4476 raise HTTPException (status_code = 404 , detail = f"Alat { clean_id } tidak ditemukan." )
4577
46- # Cek apakah sudah ada yang punya
4778 if device .owner_uid :
4879 if device .owner_uid == user_uid :
4980 return {"message" : "Alat ini memang sudah punya kamu kok." }
@@ -60,13 +91,66 @@ def claim_device(
6091 return {"status" : "success" , "message" : f"Alat { clean_id } berhasil diklaim!" }
6192
6293
63- # --- 2. ENDPOINT CONTROL RELAY ---
64- @router .post ("/control-relay" , dependencies = [Depends (RateLimiter (times = 10 , seconds = 60 ))])
94+ # --- C. UPDATE DEVICE NAME ---
95+ # Limit: 10 request / menit
96+ @router .put ("/{device_id}" , response_model = DeviceResponse , dependencies = [Depends (RateLimiter (times = 10 , seconds = 60 ))])
97+ def update_device_name (
98+ device_id : str ,
99+ update_data : UpdateDeviceSchema ,
100+ db : Session = Depends (get_db ),
101+ user_uid : str = Depends (get_current_user ) # 🔒 Wajib Login
102+ ):
103+ clean_id = device_id .strip ().upper ()
104+
105+ device = db .query (Device ).filter (Device .device_id == clean_id ).first ()
106+
107+ if not device :
108+ raise HTTPException (status_code = 404 , detail = "Alat tidak ditemukan" )
109+
110+ if device .owner_uid != user_uid :
111+ raise HTTPException (status_code = 403 , detail = "Bukan alat kamu!" )
112+
113+ device .device_name = update_data .device_name
114+ db .commit ()
115+ db .refresh (device )
116+
117+ return device
118+
119+
120+ # --- D. UNCLAIM / DELETE DEVICE ---
121+ # Limit: 5 request / menit (Tindakan berbahaya/kritis)
122+ @router .delete ("/{device_id}" , dependencies = [Depends (RateLimiter (times = 5 , seconds = 60 ))])
123+ def unclaim_device (
124+ device_id : str ,
125+ db : Session = Depends (get_db ),
126+ user_uid : str = Depends (get_current_user ) # 🔒 Wajib Login
127+ ):
128+ clean_id = device_id .strip ().upper ()
129+
130+ device = db .query (Device ).filter (Device .device_id == clean_id ).first ()
131+
132+ if not device :
133+ raise HTTPException (status_code = 404 , detail = "Alat tidak ditemukan" )
134+
135+ if device .owner_uid != user_uid :
136+ raise HTTPException (status_code = 403 , detail = "Bukan alat kamu!" )
137+
138+ # Reset kepemilikan
139+ device .owner_uid = None
140+ device .device_name = "Unclaimed Device"
141+ db .commit ()
142+
143+ return {"message" : f"Alat { clean_id } berhasil dihapus dari akunmu." }
144+
145+
146+ # --- E. CONTROL RELAY ---
147+ # Limit: 20 request / menit (Biar bisa on/off agak cepat)
148+ @router .post ("/control-relay" , dependencies = [Depends (RateLimiter (times = 20 , seconds = 60 ))])
65149def control_relay (
66150 device_id : str ,
67151 state : str ,
68152 db : Session = Depends (get_db ),
69- user_uid : str = Depends (get_current_user ) # <-- Wajib Login
153+ user_uid : str = Depends (get_current_user ) # 🔒 Wajib Login
70154):
71155 clean_id = device_id .strip ().upper ()
72156
@@ -78,10 +162,8 @@ def control_relay(
78162 if not device :
79163 raise HTTPException (status_code = 404 , detail = "Alat tidak ditemukan." )
80164
81- # --- SECURITY CHECK (PENTING!) ---
82- # Pastikan yang request adalah pemilik alat
83165 if device .owner_uid != user_uid :
84- raise HTTPException (status_code = 403 , detail = "Eits! Bukan alat kamu, jangan iseng ya! " )
166+ raise HTTPException (status_code = 403 , detail = "Eits! Bukan alat kamu. " )
85167
86168 topic = f"alat/{ clean_id } /command"
87169 payload = f'{{"relay": "{ state } "}}'
@@ -91,7 +173,9 @@ def control_relay(
91173 return {"message" : "Perintah dikirim" , "topic" : topic , "state" : state }
92174
93175
94- # --- 3. ENDPOINT AUTO REGISTER (ESP32 - Machine to Machine) ---
176+ # --- F. AUTO REGISTER (Mesin ke Mesin) ---
177+ # Limit: 5 request / menit
178+ # TIDAK PAKAI 'user_uid' KARENA YANG REQUEST ADALAH ESP32 (Bukan Manusia)
95179@router .post ("/auto-register" , dependencies = [Depends (RateLimiter (times = 5 , seconds = 60 ))])
96180def auto_register_device (
97181 data : AutoRegisterSchema ,
0 commit comments