precedentai / shared /feedback.py
lothnic's picture
Release code against the existing immutable corpus
df1329e verified
Raw History Blame Contribute Delete
4.45 kB
"""Owned, consented feedback with durable duplicate and storage limits."""
from __future__ import annotations
import hashlib
import json
import re
from uuid import UUID
from shared.database import transaction
LOCK = 5787785515774603126
RETENTION_DAYS = 7
MAX_ROWS = 500
MAX_BYTES = 49152
class FeedbackError(RuntimeError):
def __init__(self, message: str, *, status: int = 400):
self.status = status
super().__init__(message)
def cleanup_feedback(conn, batch: int = 100) -> int:
return conn.execute(
"DELETE FROM precedent_private.feedback WHERE id IN "
"(SELECT id FROM precedent_private.feedback "
"WHERE created_at <= clock_timestamp()-interval '7 days' "
"ORDER BY created_at LIMIT %s)", (batch,),
).rowcount
def _receipt(row, *, duplicate: bool = False) -> dict:
return {"id": str(row["id"]), "created_at": row["created_at"].isoformat(),
"status": "withdrawn" if row["withdrawn_at"] else "received",
"already_received": duplicate, "retention_days": RETENTION_DAYS}
class Feedback:
def __init__(self, dsn: str):
self.dsn = dsn
@staticmethod
def _owner(owner: str) -> str:
if not re.fullmatch(r"[a-f0-9]{32}", owner):
raise FeedbackError("Use the authenticated website proxy.", status=401)
return owner
def submit(self, owner: str, ident: UUID, payload: dict) -> dict:
from psycopg.types.json import Jsonb
owner = self._owner(owner)
if payload.get("consent") is not True:
raise FeedbackError("Confirm sharing this report before submitting it.")
if payload.get("context") is not None and payload.get("share_context") is not True:
raise FeedbackError("Including a question and answer needs separate consent.")
encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True).encode()
if len(encoded) > MAX_BYTES:
raise FeedbackError("Please shorten the report or included answer.", status=413)
fingerprint = hashlib.sha256(encoded).hexdigest()
with transaction(self.dsn, lock=LOCK) as conn:
cleanup_feedback(conn)
existing = conn.execute(
"SELECT * FROM precedent_private.feedback WHERE id=%s", (ident,),
).fetchone()
if existing:
if existing["owner_key"] != owner or existing["fingerprint"] != fingerprint:
raise FeedbackError("This submission ID belongs to a different report.", status=409)
return _receipt(existing, duplicate=True)
counts = conn.execute(
"SELECT count(*) AS total, "
"count(*) FILTER (WHERE created_at>clock_timestamp()-interval '1 day') AS daily, "
"count(*) FILTER (WHERE owner_key=%s AND "
"created_at>clock_timestamp()-interval '1 day') AS personal "
"FROM precedent_private.feedback", (owner,),
).fetchone()
if counts["total"] >= MAX_ROWS or counts["daily"] >= 100 or counts["personal"] >= 10:
raise FeedbackError("The feedback allowance is full. Please try again tomorrow.", status=429)
row = conn.execute(
"INSERT INTO precedent_private.feedback(id,owner_key,fingerprint,category,payload) "
"VALUES (%s,%s,%s,%s,%s) RETURNING *",
(ident, owner, fingerprint, payload["category"], Jsonb(payload)),
).fetchone()
return _receipt(row)
def list(self, owner: str) -> list[dict]:
owner = self._owner(owner)
with transaction(self.dsn) as conn:
rows = conn.execute(
"SELECT * FROM precedent_private.feedback WHERE owner_key=%s "
"AND withdrawn_at IS NULL AND created_at>clock_timestamp()-interval '7 days' "
"ORDER BY created_at DESC,id LIMIT 20", (owner,),
).fetchall()
return [{**_receipt(row), "report": row["payload"]} for row in rows]
def withdraw(self, owner: str, ident: UUID) -> None:
owner = self._owner(owner)
with transaction(self.dsn, lock=LOCK) as conn:
conn.execute(
"UPDATE precedent_private.feedback SET payload='{}'::jsonb, "
"withdrawn_at=COALESCE(withdrawn_at,clock_timestamp()) "
"WHERE id=%s AND owner_key=%s", (ident, owner),
)