A member whose account has not accepted the terms in force now gets one dialog on the action they attempted instead of a dead-end refusal. The client handler is the single TermsGate, wired into every Http POST helper so the four optimistic controllers cannot swallow the gate into an error flash, and the original request is replayed once the acceptance is recorded. Reading the site and deleting an account stay unblocked. apple.md is the source brief the compliance research documents reference. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
85 lines
2.5 KiB
Python
85 lines
2.5 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import HTMLResponse
|
|
|
|
from devplacepy.database import _now_iso, get_setting, get_table, set_consent
|
|
from devplacepy.responses import action_result, respond
|
|
from devplacepy.schemas import AcceptTermsOut
|
|
from devplacepy.seo import base_seo_context
|
|
from devplacepy.services.audit import record as audit
|
|
from devplacepy.utils import clear_user_cache, require_user
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
TERMS_ACCEPTANCE_CODE = "terms_acceptance_required"
|
|
|
|
|
|
def current_terms_version() -> str:
|
|
return get_setting("terms_version", "1") or "1"
|
|
|
|
|
|
def needs_acceptance(user: dict | None) -> bool:
|
|
if not user:
|
|
return False
|
|
return (user.get("terms_version") or "") != current_terms_version()
|
|
|
|
|
|
@router.get("/accept-terms", response_class=HTMLResponse)
|
|
async def accept_terms_page(request: Request):
|
|
user = require_user(request)
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title="Accept the updated terms",
|
|
description="The Terms of Service changed. Accept the new version to continue.",
|
|
robots="noindex,nofollow",
|
|
)
|
|
return respond(
|
|
request,
|
|
"accept_terms.html",
|
|
{
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": user,
|
|
"terms_version": current_terms_version(),
|
|
"accepted_version": user.get("terms_version") or "",
|
|
},
|
|
model=AcceptTermsOut,
|
|
)
|
|
|
|
|
|
@router.post("/accept-terms")
|
|
async def accept_terms(request: Request):
|
|
user = require_user(request)
|
|
version = current_terms_version()
|
|
now = _now_iso()
|
|
get_table("users").update(
|
|
{"uid": user["uid"], "terms_version": version, "terms_accepted_at": now},
|
|
["uid"],
|
|
)
|
|
set_consent("user", user["uid"], "terms", True, version=version)
|
|
set_consent(
|
|
"user",
|
|
user["uid"],
|
|
"privacy",
|
|
True,
|
|
version=get_setting("privacy_version", "1") or "1",
|
|
)
|
|
clear_user_cache(user["uid"])
|
|
logger.info(f"{user['username']} accepted terms version {version}")
|
|
audit.record(
|
|
request,
|
|
"terms.accept",
|
|
user=user,
|
|
target_type="user",
|
|
target_uid=user["uid"],
|
|
target_label=user.get("username"),
|
|
new_value=version,
|
|
summary=f"{user['username']} accepted terms version {version}",
|
|
links=[audit.target("user", user["uid"], user.get("username"))],
|
|
)
|
|
return action_result(request, "/feed", data={"terms_version": version})
|