from datetime import datetime, timezone from werkzeug.security import check_password_hash, generate_password_hash from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def utcnow(): return datetime.now(timezone.utc) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(120), unique=True, nullable=False) email = db.Column(db.String(255), unique=True, nullable=False) display_name = db.Column(db.String(255), nullable=False) password_hash = db.Column(db.String(255), nullable=False) role = db.Column(db.String(20), nullable=False, default='streamer') active = db.Column(db.Boolean, nullable=False, default=True) force_password_change = db.Column(db.Boolean, nullable=False, default=True) reset_token = db.Column(db.String(255), nullable=True) reset_token_expires_at = db.Column(db.DateTime(timezone=True), nullable=True) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow) stream = db.relationship('Stream', back_populates='user', uselist=False) def set_password(self, raw: str): self.password_hash = generate_password_hash(raw) def check_password(self, raw: str) -> bool: return check_password_hash(self.password_hash, raw) class Stream(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=True) name = db.Column(db.String(255), nullable=False) slug = db.Column(db.String(120), unique=True, nullable=False) token = db.Column(db.String(255), nullable=False) chat_type = db.Column(db.String(30), nullable=False, default='none') chat_url = db.Column(db.Text, nullable=True) notes = db.Column(db.Text, nullable=True) active = db.Column(db.Boolean, nullable=False, default=True) is_live = db.Column(db.Boolean, nullable=False, default=False) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow) user = db.relationship('User', back_populates='stream') sessions = db.relationship('StreamSession', back_populates='stream', cascade='all, delete-orphan') logs = db.relationship('EventLog', back_populates='stream', cascade='all, delete-orphan') class StreamSession(db.Model): id = db.Column(db.Integer, primary_key=True) stream_id = db.Column(db.Integer, db.ForeignKey('stream.id'), nullable=False) started_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) ended_at = db.Column(db.DateTime(timezone=True), nullable=True) status = db.Column(db.String(50), nullable=False, default='offline') source_type = db.Column(db.String(50), nullable=True) source_id = db.Column(db.String(120), nullable=True) publisher_ip = db.Column(db.String(64), nullable=True) video_codec = db.Column(db.String(64), nullable=True) audio_codec = db.Column(db.String(64), nullable=True) audio_channels = db.Column(db.String(32), nullable=True) audio_sample_rate = db.Column(db.String(32), nullable=True) width = db.Column(db.Integer, nullable=True) height = db.Column(db.Integer, nullable=True) fps = db.Column(db.String(32), nullable=True) bitrate = db.Column(db.String(64), nullable=True) stream = db.relationship('Stream', back_populates='sessions') class EventLog(db.Model): id = db.Column(db.Integer, primary_key=True) stream_id = db.Column(db.Integer, db.ForeignKey('stream.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) level = db.Column(db.String(20), nullable=False, default='info') message = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=utcnow) stream = db.relationship('Stream', back_populates='logs')