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
breakpilot-pwa/backend/content_service/models.py
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

165 lines
5.1 KiB
Python

"""
BreakPilot Content Service - Database Models
Educational Content Management mit Creative Commons Lizenzen
"""
from sqlalchemy import Column, String, Integer, Float, DateTime, JSON, ForeignKey, Enum, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
import enum
import uuid
Base = declarative_base()
# Content Type Enum
class ContentType(str, enum.Enum):
VIDEO = "video"
PDF = "pdf"
IMAGE_GALLERY = "image_gallery"
MARKDOWN = "markdown"
AUDIO = "audio"
H5P = "h5p"
# CC License Enum
class CCLicense(str, enum.Enum):
CC_BY = "CC-BY-4.0"
CC_BY_SA = "CC-BY-SA-4.0"
CC_BY_NC = "CC-BY-NC-4.0"
CC_BY_NC_SA = "CC-BY-NC-SA-4.0"
CC0 = "CC0-1.0"
# Category Enum
class ContentCategory(str, enum.Enum):
MOVEMENT = "movement"
MATH = "math"
STEAM = "steam"
LANGUAGE = "language"
ARTS = "arts"
SOCIAL = "social"
MINDFULNESS = "mindfulness"
# Content Status
class ContentStatus(str, enum.Enum):
DRAFT = "draft"
REVIEW = "review"
PUBLISHED = "published"
ARCHIVED = "archived"
# Many-to-Many: Content <-> Tags
content_tags = Table(
'content_tags',
Base.metadata,
Column('content_id', String, ForeignKey('contents.id')),
Column('tag_id', String, ForeignKey('tags.id'))
)
class Content(Base):
"""Educational Content Model"""
__tablename__ = 'contents'
# Primary Key
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
# Creator Info
creator_id = Column(String, nullable=False, index=True) # User ID from consent-service
creator_name = Column(String, nullable=False)
creator_email = Column(String)
# Content Metadata
title = Column(String(500), nullable=False, index=True)
description = Column(String(5000))
content_type = Column(Enum(ContentType), nullable=False)
category = Column(Enum(ContentCategory), nullable=False, index=True)
# License
license = Column(Enum(CCLicense), nullable=False, default=CCLicense.CC_BY_SA)
# Age Range
age_min = Column(Integer, default=6)
age_max = Column(Integer, default=18)
# Files & URLs
files = Column(JSON, default=list) # List of file URLs in MinIO
thumbnail_url = Column(String)
embed_url = Column(String) # For YouTube, Vimeo, etc.
# H5P Specific
h5p_content_id = Column(String) # H5P content ID if type=h5p
# Matrix Integration
matrix_room_id = Column(String) # Associated Matrix room for discussion
matrix_event_id = Column(String) # Matrix message event ID
# Status
status = Column(Enum(ContentStatus), default=ContentStatus.DRAFT, index=True)
# Analytics
downloads = Column(Integer, default=0)
views = Column(Integer, default=0)
avg_rating = Column(Float, default=0.0)
rating_count = Column(Integer, default=0)
impact_score = Column(Float, default=0.0) # Future: Impact-Scoring
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
published_at = Column(DateTime)
# Relationships
ratings = relationship("Rating", back_populates="content", cascade="all, delete-orphan")
tags = relationship("Tag", secondary=content_tags, back_populates="contents")
def __repr__(self):
return f"<Content {self.title} ({self.content_type})>"
class Rating(Base):
"""Content Ratings by Teachers"""
__tablename__ = 'ratings'
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
content_id = Column(String, ForeignKey('contents.id'), nullable=False)
user_id = Column(String, nullable=False, index=True) # Teacher ID
user_name = Column(String)
stars = Column(Integer, nullable=False) # 1-5
comment = Column(String(2000))
created_at = Column(DateTime, default=datetime.utcnow)
# Relationship
content = relationship("Content", back_populates="ratings")
def __repr__(self):
return f"<Rating {self.stars}★ for Content {self.content_id}>"
class Tag(Base):
"""Content Tags for Search/Filter"""
__tablename__ = 'tags'
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
name = Column(String(100), unique=True, nullable=False, index=True)
category = Column(String(100)) # Optional grouping
# Relationship
contents = relationship("Content", secondary=content_tags, back_populates="tags")
def __repr__(self):
return f"<Tag {self.name}>"
class Download(Base):
"""Download Tracking (für Impact-Scoring)"""
__tablename__ = 'downloads'
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
content_id = Column(String, ForeignKey('contents.id'), nullable=False, index=True)
user_id = Column(String, nullable=False, index=True) # Teacher ID
downloaded_at = Column(DateTime, default=datetime.utcnow, index=True)
ip_address = Column(String) # Optional, anonymisiert nach 7 Tagen
def __repr__(self):
return f"<Download {self.content_id} by {self.user_id}>"