From 3dc032b2d3ebf63feb24cfd4ca5963458c51e00c Mon Sep 17 00:00:00 2001 From: root Date: Thu, 18 Dec 2025 12:37:17 +0100 Subject: [PATCH] plan replacement and parameter fixes --- .../__pycache__/config.cpython-310.pyc | Bin 697 -> 777 bytes .../__pycache__/floor.cpython-310.pyc | Bin 0 -> 3263 bytes .../__pycache__/tracker_zone.cpython-310.pyc | Bin 0 -> 3558 bytes .../__pycache__/zone.cpython-310.pyc | Bin 3246 -> 3246 bytes logica_reslevis/config.py | 19 +- logica_reslevis/{plan.py => floor.py} | 22 +- logica_reslevis/tracker_zone.py | 76 +++ .../__pycache__/gateway_item.cpython-310.pyc | Bin 3988 -> 3921 bytes models/{plan_item.py => floor_item.py} | 12 +- models/gateway_item.py | 21 +- routes/__pycache__/reslevis.cpython-310.pyc | Bin 9090 -> 5598 bytes routes/reslevis.py | 555 +++--------------- schemas/__pycache__/reslevis.cpython-310.pyc | Bin 4011 -> 4307 bytes schemas/reslevis.py | 32 +- security.py.ok | 105 ---- 15 files changed, 213 insertions(+), 629 deletions(-) create mode 100644 logica_reslevis/__pycache__/floor.cpython-310.pyc create mode 100644 logica_reslevis/__pycache__/tracker_zone.cpython-310.pyc rename logica_reslevis/{plan.py => floor.py} (75%) create mode 100644 logica_reslevis/tracker_zone.py rename models/{plan_item.py => floor_item.py} (93%) delete mode 100644 security.py.ok diff --git a/logica_reslevis/__pycache__/config.cpython-310.pyc b/logica_reslevis/__pycache__/config.cpython-310.pyc index d68112b38331995586b3222dc423edb8350d1717..7cb08167170cc8a463eb9fec13bab19b3e16d293 100644 GIT binary patch delta 256 zcmdnV+R4V3&&$ij00jPboHHGmC-TWKc1+Z+=S$^C;ge>F5=vnTX3*rHxWT+$Fg>v( zwLGyhJ}0xdL@%p2Kd*{6sWdYuB{MG_!sSlO$Zu#wX?^7Uk+?se&xz1{(sk0d6A5Ca{T`!jt0| v3)%SHeEj``CSPGJ<>#eBm^>jxiOJciMNlpN1*t`eCHYV;PjG2cR%&ty zM3T!fC$T6O$_8r#vo(cou@@wkWaMN{p3Rua$`|0{=r@^*sciBLCU-`a$q$%RnRplh Dz?wh} diff --git a/logica_reslevis/__pycache__/floor.cpython-310.pyc b/logica_reslevis/__pycache__/floor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d7013f23327851f48f934c2ec4494f701717fee GIT binary patch literal 3263 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 GI{yK{q9;B8 literal 0 HcmV?d00001 diff --git a/logica_reslevis/__pycache__/zone.cpython-310.pyc b/logica_reslevis/__pycache__/zone.cpython-310.pyc index 302001a9175c5e363e82a9e8e32fe53a79a097aa..0b11b5d1ca8a4fc9d0413d067e915e067f479164 100644 GIT binary patch delta 20 acmZ1{xlWQhpO=@50SH>pIc?-#%mV;4Yy~X< delta 20 acmZ1{xlWQhpO=@50SE%wj5l&G<^cdM1q31h diff --git a/logica_reslevis/config.py b/logica_reslevis/config.py index 33714e3..a26ff10 100644 --- a/logica_reslevis/config.py +++ b/logica_reslevis/config.py @@ -14,13 +14,14 @@ DATA_DIR = Path( ) # Percorsi dei file JSON -GATEWAY_JSON_PATH = DATA_DIR / "Gateway.json" -BUILDING_JSON_PATH = DATA_DIR / "Building.json" -PLAN_JSON_PATH = DATA_DIR / "Plan.json" -ZONE_JSON_PATH = DATA_DIR / "Zone.json" -TRACKER_JSON_PATH = DATA_DIR / "Tracker.json" -OPERATOR_JSON_PATH = DATA_DIR / "Operator.json" -SUBJECT_JSON_PATH = DATA_DIR / "Subject.json" -ALARM_JSON_PATH = DATA_DIR / "Alarm.json" -TRACK_JSON_PATH = DATA_DIR / "Track.json" +GATEWAY_JSON_PATH = DATA_DIR / "gateway_list.json" +BUILDING_JSON_PATH = DATA_DIR / "building.json" +FLOOR_JSON_PATH = DATA_DIR / "floors.json" +ZONE_JSON_PATH = DATA_DIR / "zone_list.json" +TRACKER_JSON_PATH = DATA_DIR / "tracker_conf.json" +OPERATOR_JSON_PATH = DATA_DIR / "oper_conf.json" +SUBJECT_JSON_PATH = DATA_DIR / "subject.json" +ALARM_JSON_PATH = DATA_DIR / "active_alarm.json" +TRACK_JSON_PATH = DATA_DIR / "tracks.json" +TRACKER_ZONE_JSON_PATH = DATA_DIR / "tracker_zone.json" diff --git a/logica_reslevis/plan.py b/logica_reslevis/floor.py similarity index 75% rename from logica_reslevis/plan.py rename to logica_reslevis/floor.py index 32a8181..5bcf120 100644 --- a/logica_reslevis/plan.py +++ b/logica_reslevis/floor.py @@ -3,13 +3,13 @@ from typing import List, Dict, Any, Optional from fastapi.encoders import jsonable_encoder -from schemas.reslevis import PlanItem -from .config import PLAN_JSON_PATH +from schemas.reslevis import FloorItem +from .config import FLOOR_JSON_PATH from .gateway import _LockedFile, _atomic_write, _norm_str, _index_by_id -class PlanJsonRepository: - def __init__(self, json_path: str = PLAN_JSON_PATH): +class FloorJsonRepository: + def __init__(self, json_path: str = FLOOR_JSON_PATH): self.path = json_path def _read_all(self) -> List[Dict[str, Any]]: @@ -39,34 +39,34 @@ class PlanJsonRepository: ] return rows - def add(self, item: PlanItem) -> None: + def add(self, item: FloorItem) -> None: rows = self._read_all() obj = jsonable_encoder(item) obj_id = _norm_str(obj.get("id")) if any(_norm_str(r.get("id")) == obj_id for r in rows): - raise ValueError(f"Plan con id '{obj_id}' già presente") + raise ValueError(f"Floor con id '{obj_id}' già presente") rows.append(obj) self._write_all(rows) - def update(self, item: PlanItem) -> None: + def update(self, item: FloorItem) -> None: rows = self._read_all() obj = jsonable_encoder(item) obj_id = _norm_str(obj.get("id")) idx = _index_by_id(rows, obj_id) if idx is None: - raise ValueError(f"Plan con id '{obj_id}' non trovato") + raise ValueError(f"Floor con id '{obj_id}' non trovato") rows[idx] = obj self._write_all(rows) - def remove(self, plan_id: str) -> None: + def remove(self, floor_id: str) -> None: rows = self._read_all() - idx = _index_by_id(rows, plan_id) + idx = _index_by_id(rows, floor_id) if idx is None: - raise ValueError(f"Plan con id '{plan_id}' non trovato") + raise ValueError(f"Floor con id '{floor_id}' non trovato") del rows[idx] self._write_all(rows) diff --git a/logica_reslevis/tracker_zone.py b/logica_reslevis/tracker_zone.py new file mode 100644 index 0000000..a943409 --- /dev/null +++ b/logica_reslevis/tracker_zone.py @@ -0,0 +1,76 @@ +import json +from typing import List, Dict, Any, Optional +from fastapi.encoders import jsonable_encoder + +from schemas.reslevis import TrackerZoneItem +from .config import TRACKER_ZONE_JSON_PATH +from .gateway import _LockedFile, _atomic_write, _norm_str, _index_by_id + + +class TrackerZoneJsonRepository: + def __init__(self, json_path: str = TRACKER_ZONE_JSON_PATH): + self.path = json_path + + def _read_all(self) -> List[Dict[str, Any]]: + with _LockedFile(self.path, "r") as fp: + try: + fp.seek(0) + data = fp.read().strip() + return json.loads(data) if data else [] + except json.JSONDecodeError: + return [] + + def _write_all(self, rows: List[Dict[str, Any]]) -> None: + payload = json.dumps(rows, ensure_ascii=False, indent=2) + _atomic_write(self.path, payload) + + def list(self, search_string: Optional[str] = None) -> List[Dict[str, Any]]: + rows = self._read_all() + if search_string: + s = search_string.lower() + rows = [ + r + for r in rows + if any( + isinstance(r.get(k), str) and s in (r.get(k) or "").lower() + for k in ("days", "time", "id", "tracker") + ) + or any( + s in str(z).lower() + for z in (r.get("zoneList") or []) + ) + ] + return rows + + def add(self, item: TrackerZoneItem) -> None: + rows = self._read_all() + obj = jsonable_encoder(item) + obj_id = _norm_str(obj.get("id")) + + if any(_norm_str(r.get("id")) == obj_id for r in rows): + raise ValueError(f"TrackerZone con id '{obj_id}' già presente") + + rows.append(obj) + self._write_all(rows) + + def update(self, item: TrackerZoneItem) -> None: + rows = self._read_all() + obj = jsonable_encoder(item) + obj_id = _norm_str(obj.get("id")) + + idx = _index_by_id(rows, obj_id) + if idx is None: + raise ValueError(f"TrackerZone con id '{obj_id}' non trovato") + + rows[idx] = obj + self._write_all(rows) + + def remove(self, tracker_zone_id: str) -> None: + rows = self._read_all() + idx = _index_by_id(rows, tracker_zone_id) + if idx is None: + raise ValueError(f"TrackerZone con id '{tracker_zone_id}' non trovato") + + del rows[idx] + self._write_all(rows) + diff --git a/models/__pycache__/gateway_item.cpython-310.pyc b/models/__pycache__/gateway_item.cpython-310.pyc index d21116b41078fdb8ef4e11d1be9d0c83d84c1d61..dff9d8610f52480e0ab97629ee862cdd2bd38b94 100644 GIT binary patch delta 720 zcmZvY&ubGw6vy*scV{=VKe7dFD@2Q!OTnnoYHhS2_2NN-2Zca|wKS=X)Fe)(pfHO+ za?p#QKD~&DMN#x1{`eO>dZ-?QN5PAr2fh1dv7v}N%y-`B&3kX(oBI==c0`>p6gm3j zhq>14_-(Wu;1TYIcVOc;_ys@Te_^ZOEUk1p60h51xMDxxe~~+9p@fUhY*FVfh*T&) z^;JN2sG>gWl~vBzxbBp%`l}#MXiJ9;b~LALW%!h~i7jF$xoOW-3ZLeR~>L&QG`-ImfCC~0h2mA1uYYb z&7B}Zk06MkMTHhcP{DhDLcy!t_y+_*)N9WS;)T2HJLh@N%P@0xZ~x;l-ic#o;qxs{ zn$ODb`94Bk!4dKy2r`!0D|FO?ujD8B{_zW4Bldcyok87x>zHcGxlX3xhI6JSS(nLB zS*=LMJ(?eumacmQUDgdvhnvon>2UbuOl+_zli>aj6hjV>!^9X{Dnx^!5gC38ciqiO zfD`GnX5MV)a;d$XiD5K^9+Vh?#Cz#3V95x4_7d3h?(hLbj^8|f`470{_co-BJ32io zF33VRZrhS5J&;(@7qk(|!Zz)4%y5%g#7xm-IA)3_6PZj&SvVpa$Kc{seY_6SCssvO zYQzttZdp-PPF+>LVpVxv=tGDi3d{sg_<$JKdu4e zFu2J^p~H5_HjIQaTs73YgUsRmZZFn99Jq+6wAK9Dmtr~ rkoQDQcaCeB)N)$OjFwqA$7f4JM86f1P&#*z&`Eg2SI9j4=GXoJP1U(G diff --git a/models/plan_item.py b/models/floor_item.py similarity index 93% rename from models/plan_item.py rename to models/floor_item.py index 228d7ba..2714522 100644 --- a/models/plan_item.py +++ b/models/floor_item.py @@ -7,12 +7,12 @@ from attrs import field as _attrs_field from app_types import UNSET, Unset -T = TypeVar("T", bound="PlanItem") +T = TypeVar("T", bound="FloorItem") @_attrs_define -class PlanItem: - """A plan is floor or a space of a building +class FloorItem: + """A floor is space of a building Attributes: id (UUID): ID @@ -77,7 +77,7 @@ class PlanItem: else: building = UUID(_building) - plan_item = cls( + floor_item = cls( id=id, name=name, image=image, @@ -85,8 +85,8 @@ class PlanItem: building=building, ) - plan_item.additional_properties = d - return plan_item + floor_item.additional_properties = d + return floor_item @property def additional_keys(self) -> list[str]: diff --git a/models/gateway_item.py b/models/gateway_item.py index 26308ae..a05535a 100644 --- a/models/gateway_item.py +++ b/models/gateway_item.py @@ -24,7 +24,7 @@ class GatewayItem: position (Union[Unset, str]): Position x (Union[Unset, float]): X y (Union[Unset, float]): Y - zone (Union[Unset, UUID]): Zone + floor (Union[Unset, UUID]): Zone building (Union[Unset, UUID]): Building notes (Union[Unset, str]): Notes """ @@ -38,7 +38,7 @@ class GatewayItem: position: Union[Unset, str] = UNSET x: Union[Unset, float] = UNSET y: Union[Unset, float] = UNSET - zone: Union[Unset, UUID] = UNSET + floor: UUID = UNSET building: Union[Unset, UUID] = UNSET notes: Union[Unset, str] = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -62,9 +62,7 @@ class GatewayItem: y = self.y - zone: Union[Unset, str] = UNSET - if not isinstance(self.zone, Unset): - zone = str(self.zone) + floor = self.floor building: Union[Unset, str] = UNSET if not isinstance(self.building, Unset): @@ -94,8 +92,8 @@ class GatewayItem: field_dict["x"] = x if y is not UNSET: field_dict["y"] = y - if zone is not UNSET: - field_dict["zone"] = zone + if floor is not UNSET: + field_dict["floor"] = floor if building is not UNSET: field_dict["building"] = building if notes is not UNSET: @@ -124,12 +122,7 @@ class GatewayItem: y = d.pop("y", UNSET) - _zone = d.pop("zone", UNSET) - zone: Union[Unset, UUID] - if isinstance(_zone, Unset): - zone = UNSET - else: - zone = UUID(_zone) + floor = d.pop("floor", UNSET) _building = d.pop("building", UNSET) building: Union[Unset, UUID] @@ -150,7 +143,7 @@ class GatewayItem: position=position, x=x, y=y, - zone=zone, + floor=floor, building=building, notes=notes, ) diff --git a/routes/__pycache__/reslevis.cpython-310.pyc b/routes/__pycache__/reslevis.cpython-310.pyc index f90c75d4d26c25ff2af07655fee5444af55fcd17..494e849265b372e514ed8cabfa252822687bb576 100644 GIT binary patch literal 5598 zcmc&&OLH5?5#9wrU;!)$@C80ZQ4~c{f-MnxzaOM1QKBBCOeu*oNz6c)5d&uzK+P^? zleDW`C7)d7s+@AlA-<$asho1kIj2+(`2l-N<&?8;E+5?8I}1WsYzgqiK%v>Y-P_+c z^UcnKja)9H!RO!qU8%8gP5U2Sw11Ltu>?QouL(`#8rLn&)|qa{Sj>*IxQ_Q?R>B@& z1LVi8q@7|Z+hB&BW@$UaGIo|_ZIhXHj^*q@HfZNr-Y&2LB%Vx}fFrc2UfNEsHs@ zXT&_%v*N_Z=XhQWh|HwM3){MgiG|ntw$6vXPViwq@}>4#XXhd70v~-VKK6B-7e(e} zjF0n)FB6b;5wb4v$%w2eeu%OzL)H~O9m+Z^u0pLDeuT2F5wBU`mFmLlD4!!<*NNA$ zP_3d!L9KayoNC=5UJDUfC-_Oqx=FlBp{)0a*ZcewW!)lPr-7Hzh1Vi46R+FE>rALt zQ5eAMEI&uJmWbE+h^!0zB4sTTuS=n<%f#ynze-tmh}SjXmF~jpI=?}@R*2WlP_3d! z1Fu{BHr2XIyp|%emiZmZx<|ZLLRoi-*FC;US*yhBKJdzR;q`#85wH8i>tU!?QDlMF zBmS6bJs@6BBC^)`24$@gucx7`4~W+$e@0mkiPv-BRp`R&XZ+{H>k;w#MW|L$6oA)< z{FhYgG4Wy%S-;}HrmQE#>o=jSkBHY6|1D*$^U5==^y2?GihZOFXVQZYkrLcXnzPuhqBLTw#0ZRjbjEJObp zeN!|WP7UhqLZBEh3q?kVW$5-WRt?=9Q;?xRF(4m`jH!l%0wdI@@NI$!-JWb~Y$#aR z7VcJcM@mt5w{{#M_slZfQk~&=#-?yA@v7#4po3C4%|_i3TXusB%S*VGZO1b?X)Eg0 znsBP9efuW{7c21l)&S#bf7ZXzo@sCN^-|0;w|zagBy_izuwWp((d046BlF#{@~et0 zH=1sFwc@xin934IlT!v7L}{_P>t)->oWF?kqk&w%j~H5g`9 z9o*o9ejI*8b{HHa`KOU$Jf1-EgYw^Exg#qBdB;361{kZ^EbLY0saAf zIgg=i8-Yu3<6BtEwM@*=AAxr!3UAOCIVkyI3-Jmf9#$#`;qv~Apwm$2Y}-{IjXw{iLFKE6blR{-D@7?*-hWB`gIXGa+ecHCkhc+{L&&TzB*E;p zC@dj`G^&V;TaO~9_aOHj8X7^#FA$3)#3=%>u+tH&w`MKlQ}>^1iEji)w&DumMasP9lf zl7=Ae!J?PadAF&Z`%>E#p9Z^)`R*=vV7#Qe+l1ql9DoZ}aH_u(cEwo?w$d_*i77M&nlu{x|CG$4G127E z45G<{VdGX~yH>5h^q_X*YVRlu7@g>h20IQpgyFHyFfC$o7-OTIG3-M*(iKy6vmEV; z*-*zqIo9=I$3G{^B1S`uDtK^|@SnLZF@lv77@O{7+pfHvgcQ$k1e~yH?yj7|%Oo9R zB%K{3Z7ON|K`R)}e=Ha9rm2^iil={-AzsQSEb=A3OkNz`Q#$&134D9#km1FfJ1*`0 zy(FC4ge$x_ge302X)`Qwk1BE7C~>nU>A-;d8;Khaxr7GSWVwO{S2MYahPKo=`AVD- zCC)_>rvr)ar^J^<(mwcL5R*3#0nfMrN5~yZ+`?m*gQIvwF6zG1e8a4!(Qb-*v)!f!qC z8(|xW-;2a=qc-0r88;5|ZDyNjzCFb684Z_f!fj#C67D79w^hS!Biwf4muK4ux0m=m z2mBH({GKO%JJ?R*w~zSk(&pPO<0gRL3v4&dx1adEsNwcB;a*~U33q__?bC4k33q__ z9c23ncaZqK4E&NU{6>l2A$FMfy-fU$X!9MFag)IB7&}VyjS|1(8tz0B?j$=!xI@J6 zw1zuFxUP0)NtiQg+4?s60E z3VW4s$B5r+8qOx%>%{L3W)to>@p}{aWm@>XMf@h&+r;k#@hfQaU6pY&z^}-z(tIb0 zAJcHW33rW65$+W6dq=~S2zQe&1ss5N@3KeINMsweb4^@%tfr zMEoX*-;cEUK9q6$fZvbVhcw?M;`b8`_tPfa&)7$VdxiLYtl>T(+|P;MFW4u9yUaeF zFmk_qg72?fBuE~*aB93#^SKD(M|qW(nHOYFUb=MQ*zF>(`lU)aX9m%;CC|?#gUsPt z$zi4P)G434LE?f_D2FY(Qs&f3pD6hJR$)5qToQ%iyIfEwbH2(&!LNk9^hE9Ibzbzz zl|1AWgc~;L6l_A+K{?rJuTmc8)rwbw)2G3)T=8i0EJaT3qsXsqbWqZ3b1YX>+dPAk zcAH}vT|)aXg=ib@pW^;xu_gpB`;#?~i+UyxL-KOHATiE8hu{`>`R`1CGpBBl(*Jk*`$!{PBY4gJSXmCFA9R5Aobyoet8oRJ|O= z!GMW`*Z}{)^}+2^7Z{|QI=P5gg@K+%vl>hgEy3+Cn$T1S&V&^h(Z2?bdI~S#j`6t( zb&JWYl6eI#CTjTB^cvH+3#0XfA7K$cddGZVvgjl0zR6(xo_WgzRL}GdiAn;hhsExg zzcN0vblffDaeOR@yWH~%Q#`QFpLy~rka}zdY0oeCHE*&AHz9~I?iWhVlYfBktNvp` zR0JO`mEnOZSN0C)VqzHh29W||LF#hBsd0jcD2;l)2$GE_?Xe{`}~`C661U6dNKQ`MSEx0cEXOwdT>J?PAn$}KWU z1AYY4c-LZ)JJwA@4Es?Qy<@Q+Oe?fws;#B?U?as*7H0{WVme3RAjQcsu?1L&XOT*} zTqzIxqH+_~i6lAq zK~_uFQGk8<6d0A+gqbii@SDxAcp2RFRUmk`a#JNaKck+T61y8@HD8dL^=UVtSJ`DS9Y_@)Ik5)KS}?FyWUtD104)|`9lF-*1|ZG7k;P@Dwl)5A;2w$_F9ha>lIJE;gU{p;x$ts+*fNBzI z?%(3sq?ohNswYV?7&SbNHq|-dDL(LLw8ccd2sv% zowI7d@+wpVwz^b99iUyPf#&`KYPblH#i?Ne&iFf? zrKtfUk{a4V1q{Y}(>A^wdFM$udX(@+Dt@-ua?&1%5WATCF{Qv;EkM-7)TbSE{u z0uI=mlIfu`R+iOa@hUsrX(Ho?9}Ek~0$*Ax2y9$QWxR@s3F}Ph4C_qApeudQi*iw` z#cL3DYa;=w43j~r4C_Q-PL&XWMX5yAGBV13q%sPegpK)76^r!dXMCCAriw-Sg4C>U zPSJR+)%O(x>*tc$q56i>kg9KM-s(F7rDRxrYq(oR4(g%~)zECM1g|v8);zL(0~66n zwy%TZJfzAC3u-9!dV0Us3Ti5p)Q4bLD5+j|V$N3N2k!!E#zINW>`mhBqlNA*ynSJz zq`YCFqC!ok~4?d?zDEOj8k`AkEit$e*F^c?MF!do>W|J>J zQ=p8ZJknvIP2)jAjdv#vVmOa9Dj2?#G|J#eLe|6DlcYf}>rfih>{1%^YVSfC;q)sp zCQ0K4CMA>xxk71#k-L&c6|Zek?;inYY-n7xk_Lq&X|xBQQyLWfn@EFV>_QTw$ln9g zkOui=Aw^ZUlm?9l2_FkA81GIR#Bd&I)G&M}Y53qcp|UiVyAs5&!Pab{S`sj52uw-1 z#~AactFU7)dSJxv_loaux8QA88@D2cz&M%&nk1SOnlzdWnk<@LF!qpBnJN_vpd-2M zEBClWA9`1`deshv=tuWJs~bO8#gdLL{B{vbJBFnEav_#=45{Ar0_(bVzH(6)gXkS> zB}7lPSl-b`VZ{*mf`rFm@4nZ+Q@b zWteuxgD9@Gw80xhVBw>UM%+|(IBX&B!t)QI!F{uE59=hhPNBh_5rMlEv_&BB!(5D` z!EaXjP!#xiCh*Hh;CBjr69}w*1(ufrOFi*6ngSXu8U((=>2)da#U$|EAaJ#&^-_?XgzAyA4qhU?Us$QpGnIcalF8LPPBH-R+PFyb zQAA^VfB(vdJxkOuPdWP|+^4&rq_P;$cNaf2=R7X?1x#XuW^9DkzLC@tJ2rQX#KC`S z*NAIjJ#iCf+W@v1dnI-l`@jwM*BLiU>=MSCdn&j%NwYW|9b74rp|a9g*`sKf>?wPY z7Kh4>;#ee7DldzQw3x(+^0Rz&z{6gkyndgi_xq~R?+^2MlF>Nm_rID%nf3?@2-WWQ zqtPfY@RWQcF{dUAAt;iV6oE)GL_jm0n^_(eqJ{B~hS;S0Wd1p`b0-oz`MB7*6v^Vk zr&o65;8rq>6sn=+ceo%P4^zY@W6EYOs}T90eWk5 zxdsvn4aGM1nMYUhGi(Q6#B{5sxDJVJ&!s9gC;OrbR{tUN#rVCCewXXRzV z`W4>ll~*L3kA!U8w6=|fNT5X1iK!DWC^08q*ebC{i5RW{xB2@%<4%bijJxIhCgXKr z9ccmMjS{yAgY`Alyc`wD*U{lJ@q4N^j0UPMk%$wi9HeZLA??S?8|N~mvi6!cL{p-Yl} zvrs-UO>CXsEHBNuM_!7R^SIcbc#Un9GT^BuO#qU%zF!$qzz!1Kol_YRulIfybql?9r5!B*FpQY>D;C20d)rjtts`Kt;28{T^fjjhnZ zKIn34(>`d0UWus{v#<}&{8T!uuu<-tv?Ewyv-DHxu)?796R{R{h}rjvSPMJEY~Q8Q zVTJ9|Po={OJEfm?1S{;8e%cYNaJ}@eG2STsZN{6Wze5;8c9nl2i^Q;$aZmY0I!v&0 z49ChY4v{WYf0TQmHyYKL4z6N@aSL6xRAV~0h|bcLIxXYa>6JLq@-T(jq literal 4011 zcmd5<+m72*6t$f=u^lJlx%9@hKtY5{2MUzi2u&+QRisWUm3c7|k(+ZmH8^oNb_&A- zFYp6=1}}UD;v3E@5)#iyJn(>6d!NLa$;3sKibNvsIm`RlW3RRLJ|}s%8(8@K@b{(k za>ufMC2;i91lYw<{DL4XVQ1FJj%}O9PUegnaf8Q=tT}RHm&Z+We$&E;}CdVYyfXC?gDR$ zE#NK2J>XN~H1KK0YvRmptAF-)yp7ejwSP4!by86MH(mI!!Z?jt{Pva-^M9=;k;X$vxd1rsbh-CqgJ$6xz#@GA*Yj;x75Pz??==D&-@^WGM2B zf>l7v-7HT^6=M9Or8en2g}*Lr-%r$bJ}I|9N{aHzt?S#x@SYqc#Wo%=llRl&;^a`Z zFqMv?hY(nv?b#83w`|VU)4hia9-3~S={0l*ptrH08}zodcPmTAM6GJ$_R2%p({4IS z4y1OAVUkJh-I*!S@2D=2_2A-f%?aM!gEWM^W zM}g3Z7p_{+&&aOOoyyIWnJZf}T}_#_fjyL?f7dx@9n*h4Z(eS5%;gq`)qOlJ$1ZS> zulE_RRXAWARr5QH*DD+{-l%YwF#Oijooh)cze*05xo(V-p>7o@ziFXe6spWn>=W%x z@*<^@)b`ifKGZHsR6>$(pqG(kb6FC$BAfClBA5O6G>u#Y-Mo|MnL1DS84Ax*c#Z>(OZ=DU=|r2%mz2GxU@^twsbHXT*Y3pT^MKR&TO!U&7$`5S>fq$KBDuyBxsMS zjv<%Zsd|%a<5`SJUZWAW#@A`Yz3vi?-k|U%g|{eNra+~p-l4#K?OhttzgpScE+X+U zj^aE5ndx|AJBm=NG`rqfzajg-v6it?y|qlf-dda71B|s6R$^Mfs>9%RSATxOL~BxP-aW=2t4sVv{?0z)D9)6bJTm{J!C({QP3j#AWy&%G Dict[str, Any]: - global _cached_jwks - if _cached_jwks is None: - logger.info(f"Fetching JWKS from: {KEYCLOAK_JWKS_URL}") - resp = await _http.get(KEYCLOAK_JWKS_URL) - resp.raise_for_status() - _cached_jwks = resp.json() - return _cached_jwks - - -async def _get_key(token: str) -> Dict[str, Any]: - headers = jwt.get_unverified_header(token) - kid = headers.get("kid") - jwks = await _get_jwks() - for key in jwks.get("keys", []): - if key.get("kid") == kid: - return key - # chiave ruotata? invalida la cache e riprova - global _cached_jwks - _cached_jwks = None - jwks = await _get_jwks() - for key in jwks.get("keys", []): - if key.get("kid") == kid: - return key - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Signing key not found") - - -async def verify_token(token: str) -> Dict[str, Any]: - try: - key = await _get_key(token) - claims = jwt.decode( - token, - key, - algorithms=ALGORITHMS, - audience=KEYCLOAK_AUDIENCE, - issuer=KEYCLOAK_ISSUER, - options={"verify_aud": True, "verify_iss": True}, - ) - return claims - except JWTError as e: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e)) - - -async def get_current_user( - credentials: HTTPAuthorizationCredentials = Depends(http_bearer), -) -> Dict[str, Any]: - token = credentials.credentials - return await verify_token(token) - - -def require_roles(*roles: str): - async def checker(claims: Dict[str, Any] = Depends(get_current_user)) -> Dict[str, Any]: - # ruoli realm - realm_roles: List[str] = (claims.get("realm_access") or {}).get("roles", []) or [] - # ruoli client - client_roles: List[str] = [] - for v in (claims.get("resource_access") or {}).values(): - client_roles += v.get("roles", []) - have = set(realm_roles + client_roles) - missing = [r for r in roles if r not in have] - if missing: - raise HTTPException(status_code=403, detail=f"Missing roles: {missing}") - return claims - return checker -