feat: Phase 4 Batch 1 - enable enforcement for data entries
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s

- Weight, Circumference, Caliper now BLOCK on limit exceeded
- Raise HTTPException(403) with user-friendly message
- Show used/limit and suggest contacting admin
- Phase 2 → Phase 4 transition

Phase 4: Enforcement (Batch 1/3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-21 06:57:05 +01:00
parent baad096ead
commit cbcb6a2a34
3 changed files with 25 additions and 11 deletions

View File

@ -7,7 +7,7 @@ import uuid
import logging import logging
from typing import Optional from typing import Optional
from fastapi import APIRouter, Header, Depends from fastapi import APIRouter, Header, Depends, HTTPException
from db import get_db, get_cursor, r2d from db import get_db, get_cursor, r2d
from auth import require_auth, check_feature_access, increment_feature_usage from auth import require_auth, check_feature_access, increment_feature_usage
@ -35,15 +35,20 @@ def upsert_caliper(e: CaliperEntry, x_profile_id: Optional[str]=Header(default=N
"""Create or update caliper entry (upsert by date).""" """Create or update caliper entry (upsert by date)."""
pid = get_pid(x_profile_id) pid = get_pid(x_profile_id)
# Phase 2: Check feature access (non-blocking, log only) # Phase 4: Check feature access and ENFORCE
access = check_feature_access(pid, 'caliper_entries') access = check_feature_access(pid, 'caliper_entries')
log_feature_usage(pid, 'caliper_entries', access, 'create') log_feature_usage(pid, 'caliper_entries', access, 'create')
if not access['allowed']: if not access['allowed']:
logger.warning( logger.warning(
f"[FEATURE-LIMIT] User {pid} would be blocked: " f"[FEATURE-LIMIT] User {pid} blocked: "
f"caliper_entries {access['reason']} (used: {access['used']}, limit: {access['limit']})" f"caliper_entries {access['reason']} (used: {access['used']}, limit: {access['limit']})"
) )
raise HTTPException(
status_code=403,
detail=f"Limit erreicht: Du hast das Kontingent für Caliper-Einträge überschritten ({access['used']}/{access['limit']}). "
f"Bitte kontaktiere den Admin oder warte bis zum nächsten Reset."
)
with get_db() as conn: with get_db() as conn:
cur = get_cursor(conn) cur = get_cursor(conn)

View File

@ -7,7 +7,7 @@ import uuid
import logging import logging
from typing import Optional from typing import Optional
from fastapi import APIRouter, Header, Depends from fastapi import APIRouter, Header, Depends, HTTPException
from db import get_db, get_cursor, r2d from db import get_db, get_cursor, r2d
from auth import require_auth, check_feature_access, increment_feature_usage from auth import require_auth, check_feature_access, increment_feature_usage
@ -35,15 +35,20 @@ def upsert_circ(e: CircumferenceEntry, x_profile_id: Optional[str]=Header(defaul
"""Create or update circumference entry (upsert by date).""" """Create or update circumference entry (upsert by date)."""
pid = get_pid(x_profile_id) pid = get_pid(x_profile_id)
# Phase 2: Check feature access (non-blocking, log only) # Phase 4: Check feature access and ENFORCE
access = check_feature_access(pid, 'circumference_entries') access = check_feature_access(pid, 'circumference_entries')
log_feature_usage(pid, 'circumference_entries', access, 'create') log_feature_usage(pid, 'circumference_entries', access, 'create')
if not access['allowed']: if not access['allowed']:
logger.warning( logger.warning(
f"[FEATURE-LIMIT] User {pid} would be blocked: " f"[FEATURE-LIMIT] User {pid} blocked: "
f"circumference_entries {access['reason']} (used: {access['used']}, limit: {access['limit']})" f"circumference_entries {access['reason']} (used: {access['used']}, limit: {access['limit']})"
) )
raise HTTPException(
status_code=403,
detail=f"Limit erreicht: Du hast das Kontingent für Umfangs-Einträge überschritten ({access['used']}/{access['limit']}). "
f"Bitte kontaktiere den Admin oder warte bis zum nächsten Reset."
)
with get_db() as conn: with get_db() as conn:
cur = get_cursor(conn) cur = get_cursor(conn)

View File

@ -7,7 +7,7 @@ import uuid
import logging import logging
from typing import Optional from typing import Optional
from fastapi import APIRouter, Header, Depends from fastapi import APIRouter, Header, Depends, HTTPException
from db import get_db, get_cursor, r2d from db import get_db, get_cursor, r2d
from auth import require_auth, check_feature_access, increment_feature_usage from auth import require_auth, check_feature_access, increment_feature_usage
@ -35,19 +35,23 @@ def upsert_weight(e: WeightEntry, x_profile_id: Optional[str]=Header(default=Non
"""Create or update weight entry (upsert by date).""" """Create or update weight entry (upsert by date)."""
pid = get_pid(x_profile_id) pid = get_pid(x_profile_id)
# Phase 2: Check feature access (non-blocking, log only) # Phase 4: Check feature access and ENFORCE
access = check_feature_access(pid, 'weight_entries') access = check_feature_access(pid, 'weight_entries')
# Structured logging (always) # Structured logging (always)
log_feature_usage(pid, 'weight_entries', access, 'create') log_feature_usage(pid, 'weight_entries', access, 'create')
# Warning if limit exceeded (legacy) # BLOCK if limit exceeded
if not access['allowed']: if not access['allowed']:
logger.warning( logger.warning(
f"[FEATURE-LIMIT] User {pid} would be blocked: " f"[FEATURE-LIMIT] User {pid} blocked: "
f"weight_entries {access['reason']} (used: {access['used']}, limit: {access['limit']})" f"weight_entries {access['reason']} (used: {access['used']}, limit: {access['limit']})"
) )
# NOTE: Phase 2 does NOT block - just logs! raise HTTPException(
status_code=403,
detail=f"Limit erreicht: Du hast das Kontingent für Gewichtseinträge überschritten ({access['used']}/{access['limit']}). "
f"Bitte kontaktiere den Admin oder warte bis zum nächsten Reset."
)
with get_db() as conn: with get_db() as conn:
cur = get_cursor(conn) cur = get_cursor(conn)