Release v0.1.5

This commit is contained in:
2026-07-07 15:49:06 +02:00
parent f37c6668e5
commit efb69b3d2d
43 changed files with 6464 additions and 192 deletions

View File

@@ -0,0 +1,57 @@
from __future__ import annotations
from collections.abc import Iterable, Mapping, Sequence
from sqlalchemy import func
from sqlalchemy.orm import Session
from govoplan_access.backend.db.models import Account, ApiKey, Role, User
from govoplan_core.core.access import AccessAdministration
class SqlAccessAdministration(AccessAdministration):
def system_account_count(self, session: object) -> int:
db = _session(session)
return db.query(Account).count()
def role_count_for_tenant(self, session: object, tenant_id: str) -> int:
db = _session(session)
return db.query(Role).filter(Role.tenant_id == tenant_id).count()
def active_api_key_count_for_tenant(self, session: object, tenant_id: str) -> int:
db = _session(session)
return db.query(ApiKey).filter(ApiKey.tenant_id == tenant_id, ApiKey.revoked_at.is_(None)).count()
def actor_email_by_user_id(self, session: object, user_ids: Iterable[str]) -> Mapping[str, str | None]:
ids = {str(user_id) for user_id in user_ids if user_id}
if not ids:
return {}
db = _session(session)
rows = (
db.query(User.id, Account.email)
.join(Account, Account.id == User.account_id)
.filter(User.id.in_(ids))
.all()
)
return {user_id: email for user_id, email in rows}
def user_ids_for_actor_filter(self, session: object, *, operator: str, value: str) -> Sequence[str]:
normalized = value.casefold().strip()
if not normalized:
return ()
db = _session(session)
text = func.lower(func.coalesce(Account.email, ""))
condition = text == normalized if operator == "eq" else text.contains(normalized)
rows = (
db.query(User.id)
.join(Account, Account.id == User.account_id)
.filter(condition)
.all()
)
return tuple(user_id for (user_id,) in rows)
def _session(session: object) -> Session:
if not isinstance(session, Session):
raise TypeError("Access administration requires a SQLAlchemy Session")
return session