This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
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>
2026-02-09 09:51:32 +01:00

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