From 2bc57fefe34468c7fef38a40ce600264eb9cef76 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Fri, 10 Jul 2026 12:51:23 +0200 Subject: [PATCH] chore: sync GovOPlaN module split state --- README.md | 9 +- src/govoplan_tenancy.egg-info/PKG-INFO | 13 +- src/govoplan_tenancy.egg-info/requires.txt | 2 +- .../__pycache__/capabilities.cpython-313.pyc | Bin 1852 -> 0 bytes .../__pycache__/manifest.cpython-313.pyc | Bin 1555 -> 1566 bytes .../api/v1/__pycache__/routes.cpython-312.pyc | Bin 13367 -> 27787 bytes .../api/v1/__pycache__/routes.cpython-313.pyc | Bin 13414 -> 27785 bytes .../v1/__pycache__/schemas.cpython-312.pyc | Bin 4075 -> 5487 bytes .../v1/__pycache__/schemas.cpython-313.pyc | Bin 4229 -> 5649 bytes src/govoplan_tenancy/backend/api/v1/routes.py | 299 +++++++++++++++++- .../backend/api/v1/schemas.py | 24 ++ .../db/__pycache__/models.cpython-312.pyc | Bin 2115 -> 2123 bytes .../db/__pycache__/models.cpython-313.pyc | Bin 2117 -> 2125 bytes src/govoplan_tenancy/backend/db/models.py | 2 +- 14 files changed, 327 insertions(+), 22 deletions(-) delete mode 100644 src/govoplan_tenancy/backend/__pycache__/capabilities.cpython-313.pyc diff --git a/README.md b/README.md index 7a8f383..4dbc2c1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # GovOPlaN Tenancy -`govoplan-tenancy` owns the live `tenants` table, tenant lifecycle, and tenant -settings API route contributions during the GovOPlaN module split. +`govoplan-tenancy` owns the live `tenancy_tenants` table, tenant lifecycle, and +tenant settings API route contributions during the GovOPlaN module split. `govoplan-access` depends on this module, and core's registry inserts tenancy -before access for authenticated platform composition. The historical `tenants` -table name is intentionally preserved for migration compatibility. +before access for authenticated platform composition. Core migrations rename the +historical `tenants` table to the module-prefixed table name for existing +development databases. diff --git a/src/govoplan_tenancy.egg-info/PKG-INFO b/src/govoplan_tenancy.egg-info/PKG-INFO index 8bfe04a..ed22f4b 100644 --- a/src/govoplan_tenancy.egg-info/PKG-INFO +++ b/src/govoplan_tenancy.egg-info/PKG-INFO @@ -1,17 +1,18 @@ Metadata-Version: 2.4 Name: govoplan-tenancy -Version: 0.1.4 +Version: 0.1.6 Summary: GovOPlaN tenancy platform module. Author: GovOPlaN Requires-Python: >=3.12 Description-Content-Type: text/markdown -Requires-Dist: govoplan-core>=0.1.4 +Requires-Dist: govoplan-core>=0.1.6 # GovOPlaN Tenancy -`govoplan-tenancy` owns the live `tenants` table, tenant lifecycle, and tenant -settings API route contributions during the GovOPlaN module split. +`govoplan-tenancy` owns the live `tenancy_tenants` table, tenant lifecycle, and +tenant settings API route contributions during the GovOPlaN module split. `govoplan-access` depends on this module, and core's registry inserts tenancy -before access for authenticated platform composition. The historical `tenants` -table name is intentionally preserved for migration compatibility. +before access for authenticated platform composition. Core migrations rename the +historical `tenants` table to the module-prefixed table name for existing +development databases. diff --git a/src/govoplan_tenancy.egg-info/requires.txt b/src/govoplan_tenancy.egg-info/requires.txt index 5182e50..2e396b6 100644 --- a/src/govoplan_tenancy.egg-info/requires.txt +++ b/src/govoplan_tenancy.egg-info/requires.txt @@ -1 +1 @@ -govoplan-core>=0.1.4 +govoplan-core>=0.1.6 diff --git a/src/govoplan_tenancy/backend/__pycache__/capabilities.cpython-313.pyc b/src/govoplan_tenancy/backend/__pycache__/capabilities.cpython-313.pyc deleted file mode 100644 index 36a4539afa7a06b52417df3b7abb5ee743c70bbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1852 zcmZux%}*Og6o30^uRox~5CWmW5OBL`YZ?d#R1!6pRx%B(yz$AXtMz(qZMs>LnKdd> zr65(Rf_gxeic0T2B=`OS?SD{|NLZtA>LJRF3aUss_04|pQJ7u5c{B5yH}l^6y*bS1 zGl1=t^u>ekC4j&AAZbdZ2!|`EY=Z%Yumlx>2t}@oOJYSLQbi_mMIlN!CoQR!6iJ1; zyp*nJL_=LOlu>vflgt&E1VbGKBekN;<7vE+Y&)%IUUO9!Sl06WPGALY$EUi)a$gbG zx7}6Ct2!;F-FJM;54hTwsI=p)IRtm++D>3Lt-xwn)WNYDA#8zLj#~+&*FEjw`69F}(dNYiB| zx=kkg)-u| z|D$llUZ0`FKAtp_$%bWrL(8k{EE^#LlQ=C>K%?A6dI!Y@yvz>v zvIAQSn+v^sVXMAbPr9oUdsjce|H-%iXi82!07X#`(lEPtGCV{JU~`eQIQqgNkJvV> z01*rzq9G8;Pz^Dpk8Gq2Dby50CTd&J(`>+aqo4-tPltO$ zW53jYs{eNR>B#e;xhJ3XZhX9ZWAW!tcGg}LN`FkfQJ}B1m!HP}EyZxaucf=1^rx1& zJDfU`8XovCHTO~3hMG{t{{M}aa2v4fE=beGJCcx30U1GSx=7x|j$DiMG3=POn!GHO z=Pn6OpWP+I@s9&kJRhL5h+*7JODHy=mmS>8Ufbn=saF{8^%u4tZ$92x-y5IZ9iM&P zKlfL9jtnC787%8^cqJy$fH0LfL6`V%-$|IzhtdA;a4e!Er|eZ$)k zo;@~_JV>V9Ax=3ImTe=cBPYWrju#fYau(Talw;~gg)n)%98%d{FUOBq8Ic$f$D=3Z z5xH{KJ(gaK0(Xbp#Pg72K&Me0NP-~j!{jTtw+r|7;pRTfAE~OK9$nIeTRVOK0(PPA F{{bY`m)ZaT diff --git a/src/govoplan_tenancy/backend/__pycache__/manifest.cpython-313.pyc b/src/govoplan_tenancy/backend/__pycache__/manifest.cpython-313.pyc index 97c0deb8a6bbd97517890963cc19c24610352a5b..deaa380e67ee8f79efcadb0a2f9c42586057bb10 100644 GIT binary patch delta 47 zcmbQtGmnS+GcPX}0}w>8`flXbVB&Mv&&?~*cX14H)KAYW*=)vS&&X)Dxr})(695Su B3%CFP delta 36 qcmbQoGnt3`GcPX}0}w>4@!80&!NhE%r?=UW$)1tXWOF0)TqXdVkO+PN diff --git a/src/govoplan_tenancy/backend/api/v1/__pycache__/routes.cpython-312.pyc b/src/govoplan_tenancy/backend/api/v1/__pycache__/routes.cpython-312.pyc index 8b58cf8b1b3ad12ca83cf63bd5e63d78b4c7671b..68ff7f589d8bdf5eb02aabe46e45afd4191b7728 100644 GIT binary patch literal 27787 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 diff --git a/src/govoplan_tenancy/backend/api/v1/__pycache__/routes.cpython-313.pyc b/src/govoplan_tenancy/backend/api/v1/__pycache__/routes.cpython-313.pyc index a546619be2ff52de24c93b29177be68a86b30b39..e1963505e05c324f41e89572605ab39db4262c6c 100644 GIT binary patch literal 27785 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 delta 4785 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 Hl-cwDte^l) diff --git a/src/govoplan_tenancy/backend/api/v1/__pycache__/schemas.cpython-312.pyc b/src/govoplan_tenancy/backend/api/v1/__pycache__/schemas.cpython-312.pyc index 86adf47a858eeeb73b471abe167635c6a45b8636..1289f254b879bfe089ea4b7f134061ae9060d8d6 100644 GIT binary patch literal 5487 zcmbVQO>7&-72f3z|9?dNOSELkv@M&8ZN|0R+K!W2mf(NOP3p7-Q(>{@jO0z0q`bSd zL$wD1ZUEovo+9+5Lkrl54nfg_4?Xx;pcet;fm)zwiXtd{D`57bOZ(ni{w!&xK}&#d zXWpBgot^i+@4eYS$Kw$Re!u?xosYkYNz&i3)B5?{hWz@3EJ>eBT;j4J737>OW7}u= z3cj3=+KQnRSdJC^IY0H85hw(6!9pk(Dui?4LL?U|$YWe&W;SIWpvR4W!swr1<4q6OU$*KA?y1(ESN%0jW? z1aIlKFf}8?obYAM61PiS7*1rdR9w~9mh`;s_^;@~;LgA*Uet%0p~D;{6DCV83B%Sv z2wU*$wkYJ?eRO5pc?ZdPLdwZp%K1J7Cklo8F2X3EBp6pX+wp(lb8pJ|$r|ujgC1*u ztRb}Wa4yIrFjKTWHU@LWcM@%PhG_Ia%Pzt@$uD3n_{&AW6$&3}&J+a~D14;Zi&1a1 z*^5(etnVxdaEHPtXyhccQhi4a@B?6*qEXY(I@oT_V5<_&%sK;iMNuo-3%sBgaVs6g zvQ5XQ^H;dyR~*E1pA!MAYKY>ReIMG|r>!=#jPly6f5RY$6}5s`-jBfTv%i>KE)|6n z<-*FF`Z|KZ@rmNA5u8nNf?TX>Wy3ayp>LwnNiBwU@g zuB)GjinTG&nIGp{aYn@Is>s{=L!qvf9tsooDK8v`MklOWDy$1^8=iIPMSH~wiJ0af*ck*b zTJfZfK*(3JmYHuW)t2&}mj6T)c@}mn`|xzu%HJ0S%{sMSG2=KtiYWZ8-+}l{s)wSF zukIyBw+EkDwd9%2r6-50?93aA`oZ+(&8Mj`kY?JX@PSw|V8sa+RH{Cnu_G0%VO%m}gq+?jnySBg?NR!2G$?k31?0 zaMq9;Hn()k{yw7IR z1q~|%0`@bWuCcT`2<`osybc!cl7ZzE!FJ>}_xQO_1h|5+|AsBd8QA>95P@6>(o=x9 z_$~~57jXFw*e^o87%@m76L-ctTS2_s+7ELEo@0I=#W@iBW3)ZJ)D8Q?T_RsLanGIb zBZv{RpqZasF^{7nbYHU|Kuid|)v{q^qP@F_LE$i*ogU=w>bsyc-$U^O6qiu+?DRXR zLoo|~>k^31q`jl_FV5DEUfR668&6l+rM&%+9O-{BhS zP++9BLMU;(#*VwIMpOS){}wD>2Vt0SdB@idAt)P!pEC**Y_$R=00HA~222171nh-r zR0M(vN4Cm^u!!u$+Ve%Biu3bCGwfnez4>{v*^5(eu-Qvcj|zuMAT#U?yF0i@`Q-!g zpe!ug39OdPdpd{o=@*~crbZy?B#b6Dk|jhEo1Em*8(b0O3W--^Qo5?|!MhT&#^> z1FD{{vTGfj>>ZkWX1|pGbgy>keE`gjDtjL)jLmDi2_XFWZ>u}%(}y4-{Jh2vxv;@C z^oPwK!QzEYU-HKz3b6}-0^q{|G&`X-gxo_4@e0~SU?a z??%vyGps#??4{jQ~gh{oR+m z_V!IEWp^h}R@u$H?Ae!hYuOvS?9ldkta^{1e8FqS7k61|>w1+fda0|%F1QQ8$97>% z0R{Zz*S`VTjj$m4OMBi1CRIMESvGg?K?3_1tgtkptLSHZZvG1+ZJe6@~sOh>R$Nr^7S(;pna zpzbz*jNN4ZK7a;b(N^Q)OsWEj#jq4~3(8JGm`U^^;v>AN0}Jy0&rnC$SZPm*vvua_ zC8Hc#Q6PjYY(NbKl(MuNK}zxZ6izmK>0>WgE&VQLFuls&{eMbMd`1@{7%1Q;zeaNR zA_9EbQ_-Mzdp?E)cmNViko*f@vhb^v>+MP?6YcfkHzX4`mr(o=Rsm)JgcDk?aQJef=glDMXt6{l z_+8PNYMrZrpGnGMDS+9*%iSU9t@}HnHK5-JXa+-xqm>v zdtt+;-JRHo#+Rp7Ix5b%X*nFAa;|#j`cL4+8Z*P-064LKX0$}bnXt?Z%eTKKVZ_`% z_pSN^3%3hdb7se$l}8v_gkqvTvrV-6_G?C+-JUq_Ynr5^9fM>$M$>~~4`8<_Yf3)_ z9UviI`0`!~2t(W&G30;KJp#dL2`RG<-4LJ!fb+LqAp~hg#Gu+DV!m)WA&37LjCllY zo&ivmbOFi3PWihPjm2sYeHT2>AV%3KL$N0W zn#_?`(r?ESt+g`&H2`N_>=PFPiBPot<)un=aC!Veq2e60?$U&K9Srkfk@OoPs5d&7 z6vHD1_15|Apsbvz(0VLNhOqlC`fG zaKo80{WVx$H+^FCY2ZRpRAi9Wsj>fCN#++ai}TrG^P&`U>{62O)4@4^&tjiheD?pd;op;|O zdEdF8$HQPy65#jyKRbwCjQft~u#WjDmbv!Wn;CCGv-E(j@+i!p3F791%DcTn58 z;7l>jQZDYIK3j07JlvD=a&OAVeJP1cgu52}sQ?eegkd2r$nK~hdzL&`oc4WqkWeq6 zz7{k@s066L1q~A#05sTwMhFc78g4;*2#o;R(}MOA+6!o33))9$KcE9GXg{HYfDW~w z1B4C(I?{p;W}}H?2lz?}(ekVLe8E&ry^uGc>&>X9X6kEN!eKdQ^Bb1uzHVw<%_W%S zyQLc1gF;5jS<+k~zp7{F^|WcZZtGeuV})1oqP$RZI;>IPuvlbX%b6+&&D1jYOl>V~ z+tHS8`yw7XX!Di?F3JLT$Rc;1g3ViEvU3VlmRztAOm@lc83%V0?rFijgnJ10AudbY z3md`YKtn4C^FwlY#>su87paddNpjDO$o+CJ==arT2jqU}4b*x;>J8Rde0Ue|9;X1OareS|KjzSsO{?B3g zvoT=GvgI{2)70}>GxG|a-9lI4Zsx?%OUfxM8@3@c<~>bkBx z(>9FFaC3cJZ`tbCR#&yOslU*aY~h8*!B}a{VmN5|bVGsXg0an(=hE}$vgJ+}ih0xE z4Btyia}6k-QB>2Cit9*sY`+r{y_Q!|&}NEaNs6*o$P{zf4k*g=qMECXgcW5~=Z2Zn z^IEjP< z(bNoB^E`|l!e6i+B-iq0a(?#lY%;5xNw7ml-`>Ymz{!8GiurrBLzuU)|Y{4D9s0HA<07Tl23c%Iwm&fXeas z*20@;nZ@^`<8RrAo{zi~O>Hf{nJcr@{`krFk4xj%w(h>pm)W(-)R|p2w0*41&QvC+ zciG^!zs#m9@kvl$D6`4^vll;}ESX>u0(!1vk8Xl(1=>&-HYRR*F!<4l=F zD?NkY4JY^5puJ_K*;hNJ1s1-_zZAdS1UFHEj-q){_V)z}j$AmJd7 z*2!gOuVbi-Da`i!IJPEGoJ7%)%h0xop01qp!jKh!#H!_#G!>Ax8~*~AY2-;efnG+8 zZql)SH~)Tw$|&$%jSL9z@4m5}yQRK!Tlc?~g0JpXNK8!avi>(u%WSHdy(-b;cG5cj zza*_aHffUvZ`($iMqT?S82LWZEV=8It?Yr?6v^VPk%dvb_S$4===n)cB0VAwBr>24 z%nA`H7|)#-B+-hw|y@fnCk-h<5{ z6k{kbu(uRCnA(Ucn*+`24XeXiHTd1UnZ}tvKpy@}RH6clYAXt2P2zN;3oYA?Grz*6 zP~3#S@f<`eXZ}7ky7TzG`f;u_bQePYbeY|4GG_n8YK#Y&DBwiYUY+A`CI95nwj^DbBa^XW8mgUqGHySD+||f;U@Kvzl?_BULC0maQ>k z!+=AR0);T>WEx5kYT(#o4R*(QGnyg!789Yap^KPC#G}W&jjyAiP*JR)Xhy7VbX!Ih z6ljxLh@wK<4on!Gx|Puvu6)dtE-XOtygPEP%og^Omp?u(B~!cX#Lf)X*{9Ea$dpdc z?Xu|hy)v6?4Pkrin$0l0Z5zWHdF>61e4Ak$j^0pO+65&9Dd3HZc;g1tK71qUBNW4t z%MM!@sA{NRmH_orSaLvE3P7)3U}9Ku)q+%r!jh|&<9c~G>qzuj$Lu^-=TdtCiZNhu zbEI7b=~h2*G95b2RE6hKI2b~4&wmY5tw{O_&<6<8fM)^bw@^?tn}e2Jpth8i6qzDj zxv`2fn*<>qWDz5lJi!(%j_bCp*o`Hax;P4aNdqGT)Ljr-=659wEv>r?r>gz<)CX3I z-@u%vm)VW~r-a3uwQ<7*0Q|&5tm-hFK753sUVuwk zUn1DKr|n6G9H^0G>>NXpp${Iyjv(lFi<^;#zt*6m|~5DqoegRJ_ItZorg30`4<<@*Us=9 z&+Yap9*>W7$f3fW>RhUcD9KUiA!gB=byUWd_qzSL2QV#_vG<%PETDUxoNL z!r0%0@qY@p%EGO0gqf;4B#yk%s{&lAaZ#Mu9;pg&sd`=F&^F+3sYaaQ$?ch{0GDb& z6calesFSFA{o>SiqAI|pIx2~AY{R7*^@^Y1I&i5Ddc>g}z~NHuXX53Z#J>f&kahnH D@^K)m delta 1660 zcmah}O=ufO7@divwN_rOWUZ81c5Eq%A=)N(lBW5=KZQgliJL?PD-9uSFsh`8E!HZG zHf>`H@};EELv7~LQwbr5=28l|6cXBNFC~~zw-uDoLn*zKK-6pLyl)*x6om-(?aZ5R zX1@2mZ?(SvlilXiXf(pq@7UVe_xI`R=J1X#ThGt8hgpu5LL;oCy*qFufC$FKAh83A z4fn7HvASX-J#3WNLB$$9tVwKCv1Sh&BQ~bk_!fHv*Tsn*Qgosxc!*d_vB@4bQBT=B z2 z)&aV7LSJi?iXp!Yr{#D0UU@}JsC z00)>*&og0WelwW&g%xBY^3!5oEq1i4P4S*ny~~xNk33N(rDEHpErIdohi*+QJ=gD> zhq{r2B7@Ox`T5}3dE{ab^_7njcZ^1}T3cSKG~KIC-8bhPc5kKucKT{c3d@ z%YeK*U^M&!otC$a$0f3M0n2YHD2h0SD5ls2fMGrTH;O6hy^=x`zSHu={_F|&#+B^H z*_xiXTjTPD@Q|R@W9q%MA)ZDV`m1>^nxb@b8S8M!5y@2)=F~GL7g3P1hPz|U! z)a$;N2!?i&T(l#74JCcYNH1;mO~bB!`WCSOKu)d0^5=Lym6`bF!bWC#^~|U4W^meX z&qZ0b(tKJzFl>3pN(Ac>dr TenantAccessProvisioner: registry = get_registry() @@ -70,16 +91,169 @@ def _tenant_item(session: Session, tenant: Tenant) -> TenantAdminItem: ) -def _tenant_settings_item(tenant: Tenant) -> TenantSettingsItem: +def _tenant_settings_item(session: Session, tenant: Tenant) -> TenantSettingsItem: + system_settings = get_system_settings(session) + system_payload = system_i18n_payload(system_settings) + system_enabled = system_enabled_language_codes(system_settings.settings, default_locale=system_settings.default_locale) + enabled = tenant_enabled_language_codes(tenant.settings, system_enabled, default_locale=tenant.default_locale) return TenantSettingsItem( id=tenant.id, slug=tenant.slug, name=tenant.name, default_locale=tenant.default_locale, + available_languages=system_payload["available_languages"], + system_enabled_language_codes=system_enabled, + enabled_language_codes=enabled, settings=tenant.settings or {}, ) +def _tenant_settings_sections(item: TenantSettingsItem) -> dict[str, Any]: + payload = item.model_dump(mode="json") + return { + "identity": {"id": payload["id"], "slug": payload["slug"], "name": payload["name"]}, + "locale": {"default_locale": payload["default_locale"]}, + "languages": { + "available_languages": payload["available_languages"], + "system_enabled_language_codes": payload["system_enabled_language_codes"], + "enabled_language_codes": payload["enabled_language_codes"], + }, + "settings": payload["settings"], + } + + +def _record_tenant_settings_section_changes( + session: Session, + *, + tenant_id: str, + before: dict[str, Any], + after: dict[str, Any], + principal: ApiPrincipal, +) -> None: + for section in TENANT_SETTINGS_SECTIONS: + if before.get(section) == after.get(section): + continue + record_change( + session, + module_id=TENANCY_MODULE_ID, + collection=TENANT_SETTINGS_COLLECTION, + resource_type=TENANT_SETTINGS_RESOURCE, + resource_id=section, + operation="updated", + tenant_id=tenant_id, + actor_type="user", + actor_id=principal.user.id, + payload={"section": section}, + ) + + +def _record_tenant_list_change(session: Session, *, tenant: Tenant, operation: str, principal: ApiPrincipal) -> None: + record_change( + session, + module_id=TENANCY_MODULE_ID, + collection=TENANT_LIST_COLLECTION, + resource_type=TENANT_LIST_RESOURCE, + resource_id=tenant.id, + operation=operation, + tenant_id=None, + actor_type="user", + actor_id=principal.user.id, + payload={"slug": tenant.slug, "name": tenant.name, "is_active": tenant.is_active}, + ) + + +def _tenant_list_delta_query(session: Session, *, since_sequence: int): + return session.query(ChangeSequenceEntry).filter( + ChangeSequenceEntry.id > since_sequence, + ChangeSequenceEntry.module_id == TENANCY_MODULE_ID, + ChangeSequenceEntry.collection == TENANT_LIST_COLLECTION, + ) + + +def _tenant_list_watermark(session: Session) -> str: + sequence_id = _tenant_list_delta_query(session, since_sequence=0).with_entities(func.max(ChangeSequenceEntry.id)).scalar() + return encode_sequence_watermark(int(sequence_id or 0)) + + +def _tenant_list_delta_entries(session: Session, *, since: str, limit: int): + try: + since_sequence = decode_sequence_watermark(since) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + if sequence_watermark_is_expired( + session, + since=since_sequence, + tenant_id=None, + module_id=TENANCY_MODULE_ID, + collections=(TENANT_LIST_COLLECTION,), + ): + return None, False + entries_plus_one = _tenant_list_delta_query(session, since_sequence=since_sequence).order_by(ChangeSequenceEntry.id.asc()).limit(limit + 1).all() + has_more = len(entries_plus_one) > limit + return entries_plus_one[:limit], has_more + + +def _tenant_list_response_watermark(session: Session, *, entries, has_more: bool) -> str: + return encode_sequence_watermark(entries[-1].id) if has_more and entries else _tenant_list_watermark(session) + + +def _tenant_list_deleted_entries(entries: list[ChangeSequenceEntry], visible_tenant_ids: set[str]): + return [ + {"id": entry.resource_id, "resource_type": entry.resource_type or TENANT_LIST_RESOURCE} + for entry in entries + if entry.resource_id and (entry.operation == "deleted" or entry.resource_id not in visible_tenant_ids) + ] + + +def _tenant_settings_delta_query(session: Session, *, tenant_id: str, since_sequence: int): + return session.query(ChangeSequenceEntry).filter( + ChangeSequenceEntry.id > since_sequence, + or_( + and_( + ChangeSequenceEntry.module_id == TENANCY_MODULE_ID, + ChangeSequenceEntry.collection == TENANT_SETTINGS_COLLECTION, + ChangeSequenceEntry.tenant_id == tenant_id, + ), + and_( + ChangeSequenceEntry.module_id == ADMIN_MODULE_ID, + ChangeSequenceEntry.collection == ADMIN_SYSTEM_SETTINGS_COLLECTION, + ChangeSequenceEntry.resource_id == "languages", + ), + ), + ) + + +def _tenant_settings_watermark(session: Session, *, tenant_id: str) -> str: + sequence_id = _tenant_settings_delta_query(session, tenant_id=tenant_id, since_sequence=0).with_entities(func.max(ChangeSequenceEntry.id)).scalar() + return encode_sequence_watermark(int(sequence_id or 0)) + + +def _tenant_settings_delta_entries(session: Session, *, tenant_id: str, since: str, limit: int): + try: + since_sequence = decode_sequence_watermark(since) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + if sequence_watermark_is_expired( + session, + since=since_sequence, + tenant_id=tenant_id, + module_id=TENANCY_MODULE_ID, + collections=(TENANT_SETTINGS_COLLECTION,), + ): + return None, False + entries_plus_one = _tenant_settings_delta_query( + session, + tenant_id=tenant_id, + since_sequence=since_sequence, + ).order_by(ChangeSequenceEntry.id.asc()).limit(limit + 1).all() + has_more = len(entries_plus_one) > limit + return entries_plus_one[:limit], has_more + + +def _tenant_settings_response_watermark(session: Session, *, tenant_id: str, entries, has_more: bool) -> str: + return encode_sequence_watermark(entries[-1].id) if has_more and entries else _tenant_settings_watermark(session, tenant_id=tenant_id) + + @router.get("/tenants", response_model=TenantListResponse) def list_tenants( session: Session = Depends(get_session), @@ -89,6 +263,44 @@ def list_tenants( return TenantListResponse(tenants=[_tenant_item(session, tenant) for tenant in tenants]) +def _full_tenant_list_delta_response(session: Session) -> TenantListDeltaResponse: + tenants = session.query(Tenant).order_by(Tenant.name.asc()).all() + return TenantListDeltaResponse( + tenants=[_tenant_item(session, tenant) for tenant in tenants], + deleted=[], + watermark=_tenant_list_watermark(session), + has_more=False, + full=True, + ) + + +@router.get("/tenants/delta", response_model=TenantListDeltaResponse) +def list_tenants_delta( + since: str | None = None, + limit: int = Query(default=100, ge=1, le=500), + session: Session = Depends(get_session), + principal: ApiPrincipal = Depends(require_scope("system:tenants:read")), +): + del principal + if since is None: + return _full_tenant_list_delta_response(session) + entries, has_more = _tenant_list_delta_entries(session, since=since, limit=limit) + if entries is None: + return _full_tenant_list_delta_response(session) + changed_ids = [entry.resource_id for entry in entries if entry.resource_id and entry.operation != "deleted"] + tenants = [] + if changed_ids: + tenants = session.query(Tenant).filter(Tenant.id.in_(changed_ids)).order_by(Tenant.name.asc()).all() + visible_tenant_ids = {tenant.id for tenant in tenants} + return TenantListDeltaResponse( + tenants=[_tenant_item(session, tenant) for tenant in tenants], + deleted=_tenant_list_deleted_entries(entries, visible_tenant_ids), + watermark=_tenant_list_response_watermark(session, entries=entries, has_more=has_more), + has_more=has_more, + full=False, + ) + + @router.get("/tenants/owner-candidates", response_model=TenantOwnerCandidateListResponse) def list_tenant_owner_candidates( session: Session = Depends(get_session), @@ -119,7 +331,7 @@ def create_tenant( assert_tenant_governance_override_allowed(session, field="allow_custom_roles", value=payload.allow_custom_roles) assert_tenant_governance_override_allowed(session, field="allow_api_keys", value=payload.allow_api_keys) except AdminValidationError as exc: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc tenant = Tenant( slug=tenant_slug, name=payload.name.strip(), @@ -141,7 +353,7 @@ def create_tenant( owner_account_id=owner_account_id, ) except AdminValidationError as exc: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc audit_event( session, tenant_id=tenant.id, @@ -152,6 +364,7 @@ def create_tenant( scope="system", details={"slug": tenant.slug, "name": tenant.name, "creator_account_id": principal.account_id, "owner_account_id": owner_account_id}, ) + _record_tenant_list_change(session, tenant=tenant, operation="created", principal=principal) session.commit() return _tenant_item(session, tenant) @@ -171,6 +384,7 @@ def update_tenant( tenant = session.get(Tenant, tenant_id) if tenant is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found") + before_sections = _tenant_settings_sections(_tenant_settings_item(session, tenant)) if payload.name is not None: tenant.name = payload.name.strip() if "description" in payload.model_fields_set: @@ -190,7 +404,7 @@ def update_tenant( assert_tenant_governance_override_allowed(session, field="allow_api_keys", value=payload.allow_api_keys) tenant.allow_api_keys = payload.allow_api_keys except AdminValidationError as exc: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc if payload.is_active is not None: if not payload.is_active and tenant.id == principal.tenant_id: raise HTTPException( @@ -209,6 +423,9 @@ def update_tenant( object_id=tenant.id, details=payload.model_dump(exclude_unset=True), ) + after_sections = _tenant_settings_sections(_tenant_settings_item(session, tenant)) + _record_tenant_settings_section_changes(session, tenant_id=tenant.id, before=before_sections, after=after_sections, principal=principal) + _record_tenant_list_change(session, tenant=tenant, operation="updated", principal=principal) session.commit() return _tenant_item(session, tenant) @@ -221,7 +438,55 @@ def get_tenant_settings( tenant = session.get(Tenant, principal.tenant_id) if tenant is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found") - return _tenant_settings_item(tenant) + return _tenant_settings_item(session, tenant) + + +def _full_tenant_settings_delta_response(session: Session, tenant: Tenant) -> TenantSettingsDeltaResponse: + item = _tenant_settings_item(session, tenant) + return TenantSettingsDeltaResponse( + item=item, + sections=_tenant_settings_sections(item), + changed_sections=list(TENANT_SETTINGS_SECTIONS), + deleted=[], + watermark=_tenant_settings_watermark(session, tenant_id=tenant.id), + has_more=False, + full=True, + ) + + +@router.get("/tenant/settings/delta", response_model=TenantSettingsDeltaResponse) +def get_tenant_settings_delta( + since: str | None = None, + limit: int = Query(default=100, ge=1, le=500), + session: Session = Depends(get_session), + principal: ApiPrincipal = Depends(require_scope("admin:settings:read")), +): + tenant = session.get(Tenant, principal.tenant_id) + if tenant is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found") + if since is None: + return _full_tenant_settings_delta_response(session, tenant) + entries, has_more = _tenant_settings_delta_entries(session, tenant_id=tenant.id, since=since, limit=limit) + if entries is None: + return _full_tenant_settings_delta_response(session, tenant) + changed = set() + for entry in entries: + if entry.module_id == ADMIN_MODULE_ID and entry.collection == ADMIN_SYSTEM_SETTINGS_COLLECTION and entry.resource_id == "languages": + changed.add("languages") + elif entry.resource_type == TENANT_SETTINGS_RESOURCE: + changed.add(entry.resource_id) + changed_sections = [section for section in TENANT_SETTINGS_SECTIONS if section in changed] + item = _tenant_settings_item(session, tenant) + sections = _tenant_settings_sections(item) + return TenantSettingsDeltaResponse( + item=None, + sections={section: sections[section] for section in changed_sections}, + changed_sections=changed_sections, + deleted=[], + watermark=_tenant_settings_response_watermark(session, tenant_id=tenant.id, entries=entries, has_more=has_more), + has_more=has_more, + full=False, + ) @router.patch("/tenant/settings", response_model=TenantSettingsItem) @@ -233,7 +498,19 @@ def update_tenant_settings( tenant = session.get(Tenant, principal.tenant_id) if tenant is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tenant not found") - tenant.default_locale = payload.default_locale.strip() or "en" + before_sections = _tenant_settings_sections(_tenant_settings_item(session, tenant)) + system_settings = get_system_settings(session) + system_enabled = system_enabled_language_codes(system_settings.settings, default_locale=system_settings.default_locale) + current_i18n = i18n_settings(tenant.settings) + raw_enabled = payload.enabled_language_codes if "enabled_language_codes" in payload.model_fields_set else current_i18n.get("enabled_language_codes") + enabled = normalize_enabled_language_codes( + raw_enabled, + [{"code": code} for code in system_enabled], + default_locale=payload.default_locale, + fallback_codes=system_enabled, + ) + tenant.default_locale = payload.default_locale.strip() or enabled[0] + tenant.settings = update_i18n_settings(tenant.settings, enabled_language_codes=enabled) session.add(tenant) audit_event( session, @@ -242,7 +519,9 @@ def update_tenant_settings( action="tenant.settings.updated", object_type="tenant", object_id=tenant.id, - details={"default_locale": tenant.default_locale}, + details={"default_locale": tenant.default_locale, "enabled_language_codes": enabled}, ) + after_sections = _tenant_settings_sections(_tenant_settings_item(session, tenant)) + _record_tenant_settings_section_changes(session, tenant_id=tenant.id, before=before_sections, after=after_sections, principal=principal) session.commit() - return _tenant_settings_item(tenant) + return _tenant_settings_item(session, tenant) diff --git a/src/govoplan_tenancy/backend/api/v1/schemas.py b/src/govoplan_tenancy/backend/api/v1/schemas.py index 6566a69..9a48299 100644 --- a/src/govoplan_tenancy/backend/api/v1/schemas.py +++ b/src/govoplan_tenancy/backend/api/v1/schemas.py @@ -5,6 +5,8 @@ from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, field_validator +from govoplan_core.api.v1.schemas import DeltaDeletedItem + class TenantAdminItem(BaseModel): id: str @@ -27,6 +29,14 @@ class TenantListResponse(BaseModel): tenants: list[TenantAdminItem] +class TenantListDeltaResponse(BaseModel): + tenants: list[TenantAdminItem] = Field(default_factory=list) + deleted: list[DeltaDeletedItem] = Field(default_factory=list) + watermark: str | None = None + has_more: bool = False + full: bool = False + + class TenantOwnerCandidate(BaseModel): account_id: str email: str @@ -69,10 +79,24 @@ class TenantSettingsItem(BaseModel): slug: str name: str default_locale: str = Field(default="en", min_length=1, max_length=20) + available_languages: list[dict[str, Any]] = Field(default_factory=list) + system_enabled_language_codes: list[str] = Field(default_factory=list) + enabled_language_codes: list[str] = Field(default_factory=list) settings: dict[str, Any] = Field(default_factory=dict) +class TenantSettingsDeltaResponse(BaseModel): + item: TenantSettingsItem | None = None + sections: dict[str, Any] = Field(default_factory=dict) + changed_sections: list[str] = Field(default_factory=list) + deleted: list[DeltaDeletedItem] = Field(default_factory=list) + watermark: str | None = None + has_more: bool = False + full: bool = False + + class TenantSettingsUpdateRequest(BaseModel): model_config = ConfigDict(extra="forbid") default_locale: str = Field(min_length=1, max_length=20) + enabled_language_codes: list[str] | None = None diff --git a/src/govoplan_tenancy/backend/db/__pycache__/models.cpython-312.pyc b/src/govoplan_tenancy/backend/db/__pycache__/models.cpython-312.pyc index e876a04d7455fb8662daac1f5305ffe090aea04b..74937ed03716b3cc7d9bbef9838be16661fa1935 100644 GIT binary patch delta 44 ycmX>sa9V)(G%qg~0}wop_s=rk$a{*JlfNW2FEKBta9Du%G%qg~0}y;+^2yTP$a{*Jk$v+6=2Rv|+0AZjtV{s5oe4Go diff --git a/src/govoplan_tenancy/backend/db/__pycache__/models.cpython-313.pyc b/src/govoplan_tenancy/backend/db/__pycache__/models.cpython-313.pyc index c02b973611a489c8dc5bc6678e9362a119c45a10..c182461c1803f3a98e705d2789125ef4654f2209 100644 GIT binary patch delta 44 ycmX>qa8`i#GcPX}0}wop_s=rk$UC2zlfNW2FEKBra8!WzGcPX}0}y;+^2yTP$UC2zk$v-4=1EM9vYS2FSeXF16$v{4 diff --git a/src/govoplan_tenancy/backend/db/models.py b/src/govoplan_tenancy/backend/db/models.py index 77c2dfd..1dd0005 100644 --- a/src/govoplan_tenancy/backend/db/models.py +++ b/src/govoplan_tenancy/backend/db/models.py @@ -14,7 +14,7 @@ def new_uuid() -> str: class Tenant(Base, TimestampMixin): - __tablename__ = "tenants" + __tablename__ = "tenancy_tenants" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) slug: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)