d3c8811fdb
- TCFEncoderService: generates base64url-encoded TC Strings per IAB spec with 12 purposes, vendor consent bitfield, CMP metadata - Category-to-purpose mapping (necessary→none, statistics→1,7,8,9,10, marketing→1,2,3,4,5,6,7,12, functional→1,11) - tcf_routes: 5 endpoints (purposes, features, mapping, encode, encode-categories) - banner_consent_service: auto-generates TC String when tcf_enabled=true - TCFSettings.tsx: enable/disable toggle, purpose grid with category mapping, TC String test generator, CMP registration info - New "TCF/IAB" tab in cookie-banner page (7 tabs total) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
2.9 KiB
Python
96 lines
2.9 KiB
Python
"""
|
|
FastAPI routes for IAB TCF 2.2 (Transparency & Consent Framework).
|
|
|
|
Endpoints:
|
|
GET /tcf/purposes — list 12 IAB purposes with translations
|
|
GET /tcf/special-features — list 2 IAB special features
|
|
GET /tcf/category-mapping — banner category → IAB purpose mapping
|
|
POST /tcf/encode — generate TC String from consent decisions
|
|
POST /tcf/encode-categories — generate TC String from banner categories
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional, List, Dict
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from classroom_engine.database import get_db
|
|
from .tenant_utils import get_tenant_id as _get_tenant_id
|
|
from compliance.services.tcf_encoder_service import TCFEncoderService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/tcf", tags=["tcf"])
|
|
|
|
|
|
class TCFEncodeRequest(BaseModel):
|
|
purpose_consents: Dict[int, bool] = {}
|
|
vendor_consents: Dict[int, bool] = {}
|
|
purpose_li: Optional[Dict[int, bool]] = None
|
|
special_features: Optional[Dict[int, bool]] = None
|
|
cmp_id: int = 1
|
|
cmp_version: int = 1
|
|
consent_language: str = "DE"
|
|
|
|
|
|
class TCFCategoryEncodeRequest(BaseModel):
|
|
categories: List[str] = []
|
|
vendor_consents: Optional[Dict[int, bool]] = None
|
|
cmp_id: int = 1
|
|
consent_language: str = "DE"
|
|
|
|
|
|
@router.get("/purposes")
|
|
def list_purposes():
|
|
return TCFEncoderService.get_purposes()
|
|
|
|
|
|
@router.get("/special-features")
|
|
def list_special_features():
|
|
return TCFEncoderService.get_special_features()
|
|
|
|
|
|
@router.get("/category-mapping")
|
|
def get_category_mapping():
|
|
return TCFEncoderService.get_category_purpose_map()
|
|
|
|
|
|
@router.post("/encode")
|
|
def encode_tc_string(body: TCFEncodeRequest):
|
|
encoder = TCFEncoderService(
|
|
cmp_id=body.cmp_id,
|
|
cmp_version=body.cmp_version,
|
|
consent_language=body.consent_language,
|
|
)
|
|
tc_string = encoder.encode(
|
|
purpose_consents=body.purpose_consents,
|
|
vendor_consents=body.vendor_consents,
|
|
purpose_li=body.purpose_li,
|
|
special_features=body.special_features,
|
|
)
|
|
return {"tc_string": tc_string, "version": 2}
|
|
|
|
|
|
@router.post("/encode-categories")
|
|
def encode_from_categories(body: TCFCategoryEncodeRequest):
|
|
encoder = TCFEncoderService(
|
|
cmp_id=body.cmp_id,
|
|
consent_language=body.consent_language,
|
|
)
|
|
tc_string = encoder.encode_from_categories(
|
|
categories=body.categories,
|
|
vendor_consents=body.vendor_consents,
|
|
)
|
|
# Also return which purposes were set
|
|
from compliance.services.tcf_encoder_service import CATEGORY_PURPOSE_MAP
|
|
purpose_ids = set()
|
|
for cat in body.categories:
|
|
purpose_ids.update(CATEGORY_PURPOSE_MAP.get(cat, []))
|
|
return {
|
|
"tc_string": tc_string,
|
|
"version": 2,
|
|
"purposes_consented": sorted(purpose_ids),
|
|
"categories": body.categories,
|
|
}
|