Files
qtalker---/backend/app/schemas.py
Indiana 605f32f20e fix: blank inbox previews, two unauthenticated 500s, and the missing social tests
- Every conversation row in the inbox rendered an empty preview: the
  frontend read `last_body`, which the API has never sent. It sends
  `excerpt` plus `truncated`.
- `GET /api/codex?limit=-1` passed a negative LIMIT to Postgres -> 500.
  Now bounded by Query(ge=1, le=200) -> 422.
- Registering with a >255 char email hit the VARCHAR(255) column and
  surfaced as a 500. PATCH /api/profile already validated this; only
  /auth/register was open.

The social layer had zero tests, despite routes/messages.py citing
"tests/test_messages.py's cross-user leak tests" and routes/profile.py
claiming "privacy rules that the tests pin". Neither file existed. Both now
do: auth required on every route, email only on /api/profile/me, no id or
password_hash on any public payload, third-party thread reads empty,
payload-supplied sender inert, veiled profiles 404 and stay off the roster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 01:45:04 +00:00

74 lines
1.7 KiB
Python

import uuid
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
class RegisterRequest(BaseModel):
username: str = Field(min_length=3, max_length=32)
password: str = Field(min_length=8, max_length=128)
# users.email is VARCHAR(255): without a cap here an over-long address
# reached Postgres and surfaced as a 500 instead of a validation error.
email: str | None = Field(default=None, max_length=255)
class UserOut(BaseModel):
id: uuid.UUID
username: str
essence: int = 0
unlocks: list[str] = Field(default_factory=list)
model_config = ConfigDict(from_attributes=True)
class LoginRequest(BaseModel):
username: str
password: str
class UnlockOut(BaseModel):
unlock_key: str
unlocked_at: datetime
model_config = ConfigDict(from_attributes=True)
class PurchaseOut(BaseModel):
unlock_key: str
unlocked_at: datetime
essence: int
class InventoryItemOut(BaseModel):
id: uuid.UUID
item_type: str
item_key: str
payload: dict
obtained_at: datetime
model_config = ConfigDict(from_attributes=True)
class SigilIn(BaseModel):
name: str = Field(min_length=1, max_length=64)
design: dict
class SigilOut(BaseModel):
id: uuid.UUID
name: str
design: dict
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class MessageIn(BaseModel):
"""A whisper sent from one hunter to another (Workstream S)."""
# Length rules live in routes/messages.py, not here: an empty or
# oversized body is a 400 with in-fiction copy the UI can show, which
# is friendlier than Pydantic's 422 validation envelope.
to: str
body: str