Files
consentos/apps/api/src/routers/site_group_config.py
James Cottrill fbf26453f2 feat: initial public release
ConsentOS — a privacy-first cookie consent management platform.

Self-hosted, source-available alternative to OneTrust, Cookiebot, and
CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google
Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant
architecture with role-based access, configuration cascade
(system → org → group → site → region), dark-pattern detection in
the scanner, and a tamper-evident consent record audit trail.

This is the initial public release. Prior development history is
retained internally.

See README.md for the feature list, architecture overview, and
quick-start instructions. Licensed under the Elastic Licence 2.0 —
self-host freely; do not resell as a managed service.
2026-04-14 09:18:18 +00:00

102 lines
3.3 KiB
Python

"""Site-group-level default configuration endpoints.
Provides GET and PUT for a site group's config defaults.
These defaults sit between org defaults and site config in the cascade.
"""
import uuid
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.db import get_db
from src.models.site_group import SiteGroup
from src.models.site_group_config import SiteGroupConfig
from src.schemas.auth import CurrentUser
from src.schemas.site_group_config import SiteGroupConfigResponse, SiteGroupConfigUpdate
from src.services.dependencies import require_role
router = APIRouter(prefix="/site-groups", tags=["site-groups"])
@router.get("/{group_id}/config", response_model=SiteGroupConfigResponse)
async def get_site_group_config(
group_id: uuid.UUID,
current_user: CurrentUser = Depends(require_role("owner", "admin", "editor", "viewer")),
db: AsyncSession = Depends(get_db),
) -> SiteGroupConfig:
"""Retrieve configuration defaults for a site group."""
await _verify_group_ownership(group_id, current_user.organisation_id, db)
result = await db.execute(
select(SiteGroupConfig).where(SiteGroupConfig.site_group_id == group_id)
)
config = result.scalar_one_or_none()
if config is None:
# Auto-create an empty config row so the response is always valid
config = SiteGroupConfig(site_group_id=group_id)
db.add(config)
await db.flush()
await db.refresh(config)
return config
@router.put("/{group_id}/config", response_model=SiteGroupConfigResponse)
async def update_site_group_config(
group_id: uuid.UUID,
body: SiteGroupConfigUpdate,
current_user: CurrentUser = Depends(require_role("owner", "admin")),
db: AsyncSession = Depends(get_db),
) -> SiteGroupConfig:
"""Create or update configuration defaults for a site group.
Only non-None fields will override org/system defaults when resolving site config.
"""
await _verify_group_ownership(group_id, current_user.organisation_id, db)
result = await db.execute(
select(SiteGroupConfig).where(SiteGroupConfig.site_group_id == group_id)
)
config = result.scalar_one_or_none()
if config is None:
config = SiteGroupConfig(
site_group_id=group_id,
**body.model_dump(exclude_unset=True),
)
db.add(config)
else:
update_data = body.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
await db.flush()
await db.refresh(config)
return config
# -- Helpers ------------------------------------------------------------------
async def _verify_group_ownership(
group_id: uuid.UUID,
organisation_id: uuid.UUID,
db: AsyncSession,
) -> None:
"""Ensure the site group belongs to the user's organisation."""
result = await db.execute(
select(SiteGroup).where(
SiteGroup.id == group_id,
SiteGroup.organisation_id == organisation_id,
SiteGroup.deleted_at.is_(None),
)
)
if result.scalar_one_or_none() is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Site group not found",
)