Coverage for server / models / class_message.py: 96%
28 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
1from datetime import datetime, timezone
2from typing import Optional
3from beanie import Document
4from fastapi import HTTPException, status
5from pydantic import BaseModel, Field, field_validator
7from server.utilities.html_sanitizer import to_plain_text
10class ClassMessage(Document):
11 class_id: str
12 author_id: str
13 author_name: str
14 content: str
15 created_at: Optional[datetime] = None
17 @field_validator("created_at", mode="before")
18 @classmethod
19 def set_created_at(cls, v):
20 return v or datetime.now(timezone.utc)
22 class Settings:
23 name = "class_messages"
24 indexes = [
25 [("class_id", 1), ("created_at", -1)],
26 ]
29class CreateClassMessage(BaseModel):
30 content: str = Field(..., min_length=1, max_length=5000)
32 @field_validator("content")
33 @classmethod
34 def _strip_markup(cls, value):
35 """Store announcement text as plain text — never as markup.
37 Added by Allan Ninal — 2026-09-28 (EI-T485). A class announcement created
38 with `<script>alert('XSS')</script>hello` was stored and returned verbatim;
39 verified live on QA 2026-09-27. Mirrors the class title/description rule and
40 the existing `theme_name` validator: strip markup first, then refuse a value
41 that was nothing but markup.
42 """
43 cleaned = (to_plain_text(value) or "").strip()
44 if not cleaned:
45 raise HTTPException(
46 status_code=status.HTTP_400_BAD_REQUEST,
47 detail="content cannot be blank",
48 )
49 return cleaned