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>
This commit is contained in:
164
backend/content_service/models.py
Normal file
164
backend/content_service/models.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
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}>"
|
||||
Reference in New Issue
Block a user