A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
192 lines
4.8 KiB
Python
192 lines
4.8 KiB
Python
"""
|
|
Pydantic Schemas für Content Service API
|
|
"""
|
|
from pydantic import BaseModel, Field, validator
|
|
from typing import Optional, List
|
|
from datetime import datetime
|
|
from models import ContentType, CCLicense, ContentCategory, ContentStatus
|
|
|
|
# ============= REQUEST SCHEMAS =============
|
|
|
|
class ContentCreate(BaseModel):
|
|
"""Schema für Content Creation"""
|
|
title: str = Field(..., min_length=3, max_length=500)
|
|
description: Optional[str] = Field(None, max_length=5000)
|
|
content_type: ContentType
|
|
category: ContentCategory
|
|
license: CCLicense = CCLicense.CC_BY_SA
|
|
|
|
age_min: int = Field(6, ge=3, le=18)
|
|
age_max: int = Field(18, ge=3, le=18)
|
|
|
|
embed_url: Optional[str] = None
|
|
tags: List[str] = []
|
|
|
|
@validator('age_max')
|
|
def age_max_must_be_greater(cls, v, values):
|
|
if 'age_min' in values and v < values['age_min']:
|
|
raise ValueError('age_max must be >= age_min')
|
|
return v
|
|
|
|
class ContentUpdate(BaseModel):
|
|
"""Schema für Content Update"""
|
|
title: Optional[str] = Field(None, min_length=3, max_length=500)
|
|
description: Optional[str] = Field(None, max_length=5000)
|
|
category: Optional[ContentCategory] = None
|
|
license: Optional[CCLicense] = None
|
|
age_min: Optional[int] = Field(None, ge=3, le=18)
|
|
age_max: Optional[int] = Field(None, ge=3, le=18)
|
|
embed_url: Optional[str] = None
|
|
tags: Optional[List[str]] = None
|
|
status: Optional[ContentStatus] = None
|
|
|
|
class RatingCreate(BaseModel):
|
|
"""Schema für Rating Creation"""
|
|
stars: int = Field(..., ge=1, le=5)
|
|
comment: Optional[str] = Field(None, max_length=2000)
|
|
|
|
# ============= RESPONSE SCHEMAS =============
|
|
|
|
class TagResponse(BaseModel):
|
|
"""Tag Response"""
|
|
id: str
|
|
name: str
|
|
category: Optional[str]
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class RatingResponse(BaseModel):
|
|
"""Rating Response"""
|
|
id: str
|
|
user_id: str
|
|
user_name: Optional[str]
|
|
stars: int
|
|
comment: Optional[str]
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class ContentResponse(BaseModel):
|
|
"""Content Response (Full)"""
|
|
id: str
|
|
creator_id: str
|
|
creator_name: str
|
|
creator_email: Optional[str]
|
|
|
|
title: str
|
|
description: Optional[str]
|
|
content_type: ContentType
|
|
category: ContentCategory
|
|
license: CCLicense
|
|
|
|
age_min: int
|
|
age_max: int
|
|
|
|
files: List[str]
|
|
thumbnail_url: Optional[str]
|
|
embed_url: Optional[str]
|
|
h5p_content_id: Optional[str]
|
|
|
|
matrix_room_id: Optional[str]
|
|
matrix_event_id: Optional[str]
|
|
|
|
status: ContentStatus
|
|
|
|
downloads: int
|
|
views: int
|
|
avg_rating: float
|
|
rating_count: int
|
|
impact_score: float
|
|
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
published_at: Optional[datetime]
|
|
|
|
tags: List[TagResponse] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class ContentListItem(BaseModel):
|
|
"""Content Response (List View - simplified)"""
|
|
id: str
|
|
creator_name: str
|
|
title: str
|
|
description: Optional[str]
|
|
content_type: ContentType
|
|
category: ContentCategory
|
|
license: CCLicense
|
|
thumbnail_url: Optional[str]
|
|
avg_rating: float
|
|
rating_count: int
|
|
downloads: int
|
|
created_at: datetime
|
|
tags: List[str] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class ContentWithRatings(ContentResponse):
|
|
"""Content with ratings included"""
|
|
ratings: List[RatingResponse] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
# ============= FILTER SCHEMAS =============
|
|
|
|
class ContentFilter(BaseModel):
|
|
"""Search/Filter Parameters"""
|
|
search: Optional[str] = None
|
|
category: Optional[ContentCategory] = None
|
|
content_type: Optional[ContentType] = None
|
|
license: Optional[CCLicense] = None
|
|
age_min: Optional[int] = None
|
|
age_max: Optional[int] = None
|
|
tags: Optional[List[str]] = None
|
|
min_rating: Optional[float] = None
|
|
status: Optional[ContentStatus] = ContentStatus.PUBLISHED
|
|
creator_id: Optional[str] = None
|
|
|
|
# Pagination
|
|
skip: int = Field(0, ge=0)
|
|
limit: int = Field(20, ge=1, le=100)
|
|
|
|
# Sorting
|
|
sort_by: str = Field("created_at", pattern="^(created_at|avg_rating|downloads|title)$")
|
|
sort_desc: bool = True
|
|
|
|
# ============= ANALYTICS SCHEMAS =============
|
|
|
|
class ContentStats(BaseModel):
|
|
"""Content Statistics"""
|
|
total_contents: int
|
|
total_downloads: int
|
|
total_views: int
|
|
avg_rating: float
|
|
by_category: dict
|
|
by_type: dict
|
|
by_license: dict
|
|
|
|
class CreatorStats(BaseModel):
|
|
"""Creator Statistics"""
|
|
creator_id: str
|
|
creator_name: str
|
|
total_contents: int
|
|
total_downloads: int
|
|
total_views: int
|
|
avg_rating: float
|
|
impact_score: float
|
|
content_breakdown: dict
|
|
|
|
# ============= UPLOAD SCHEMAS =============
|
|
|
|
class FileUploadResponse(BaseModel):
|
|
"""File Upload Response"""
|
|
file_url: str
|
|
file_name: str
|
|
file_size: int
|
|
content_type: str
|