|
# retoor <retoor@molodetz.nl>
|
|
|
|
import logging
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
|
|
from devplacepy.database import (
|
|
CONSENT_KINDS,
|
|
consent_state,
|
|
get_setting,
|
|
get_table,
|
|
list_consents,
|
|
set_consent,
|
|
)
|
|
from devplacepy.dependencies import json_or_form
|
|
from devplacepy.models import ConsentForm, MaturePreferenceForm
|
|
from devplacepy.responses import action_result
|
|
from devplacepy.routers.profile.delete import _owner_only
|
|
from devplacepy.services.audit import record as audit
|
|
from devplacepy.utils import clear_user_cache
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
TRUTHY = ("1", "on", "true", "yes")
|
|
|
|
VERSION_KEYS = {"terms": "terms_version", "privacy": "privacy_version"}
|
|
|
|
|
|
def consent_view(owner_kind: str, owner_id: str) -> list[dict]:
|
|
latest = {}
|
|
for row in list_consents(owner_kind, owner_id):
|
|
latest.setdefault(row["kind"], row)
|
|
return [
|
|
{
|
|
"kind": kind,
|
|
"label": label,
|
|
"state": (latest.get(kind) or {}).get("state", "withdrawn"),
|
|
"version": (latest.get(kind) or {}).get("version", ""),
|
|
"granted_at": (latest.get(kind) or {}).get("granted_at", ""),
|
|
"withdrawn_at": (latest.get(kind) or {}).get("withdrawn_at", ""),
|
|
}
|
|
for kind, label in CONSENT_KINDS.items()
|
|
]
|
|
|
|
|
|
def consent_version(kind: str) -> str:
|
|
key = VERSION_KEYS.get(kind)
|
|
return (get_setting(key, "1") or "1") if key else "1"
|
|
|
|
|
|
@router.post("/{username}/consent")
|
|
async def set_user_consent(
|
|
request: Request,
|
|
username: str,
|
|
data: Annotated[ConsentForm, Depends(json_or_form(ConsentForm))],
|
|
):
|
|
target, denied = _owner_only(
|
|
request, username, "Only the account holder can change a consent"
|
|
)
|
|
if denied is not None:
|
|
return denied
|
|
granted = data.granted.strip().lower() in TRUTHY
|
|
before = consent_state("user", target["uid"], data.kind)
|
|
set_consent(
|
|
"user", target["uid"], data.kind, granted, version=consent_version(data.kind)
|
|
)
|
|
logger.info(
|
|
f"Consent {data.kind} {'granted' if granted else 'withdrawn'} for {target['username']}"
|
|
)
|
|
audit.record(
|
|
request,
|
|
"consent.grant" if granted else "consent.withdraw",
|
|
target_type="user",
|
|
target_uid=target["uid"],
|
|
target_label=target["username"],
|
|
old_value=(before or {}).get("state"),
|
|
new_value="granted" if granted else "withdrawn",
|
|
metadata={"kind": data.kind},
|
|
summary=(
|
|
f"{'granted' if granted else 'withdrew'} {data.kind} consent "
|
|
f"for {target['username']}"
|
|
),
|
|
links=[audit.target("user", target["uid"], target["username"])],
|
|
)
|
|
url = f"/profile/{target['username']}?tab=privacy"
|
|
return action_result(
|
|
request,
|
|
url,
|
|
data={"kind": data.kind, "state": "granted" if granted else "withdrawn"},
|
|
)
|
|
|
|
|
|
@router.post("/{username}/mature-content")
|
|
async def set_mature_preference(
|
|
request: Request,
|
|
username: str,
|
|
data: Annotated[MaturePreferenceForm, Depends(json_or_form(MaturePreferenceForm))],
|
|
):
|
|
target, denied = _owner_only(
|
|
request,
|
|
username,
|
|
"Only the account holder can change the mature-content preference",
|
|
)
|
|
if denied is not None:
|
|
return denied
|
|
opted_in = data.mature_opt_in.strip().lower() in TRUTHY
|
|
get_table("users").update(
|
|
{"uid": target["uid"], "mature_opt_in": 1 if opted_in else 0}, ["uid"]
|
|
)
|
|
clear_user_cache(target["uid"])
|
|
audit.record(
|
|
request,
|
|
"profile.mature_content",
|
|
target_type="user",
|
|
target_uid=target["uid"],
|
|
target_label=target["username"],
|
|
new_value=1 if opted_in else 0,
|
|
summary=(
|
|
f"{'enabled' if opted_in else 'disabled'} mature content "
|
|
f"for {target['username']}"
|
|
),
|
|
links=[audit.target("user", target["uid"], target["username"])],
|
|
)
|
|
url = f"/profile/{target['username']}?tab=privacy"
|
|
return action_result(request, url, data={"mature_opt_in": opted_in})
|