from flask_sqlalchemy import SQLAlchemy from flask_login import UserMixin from datetime import datetime from enum import Enum db = SQLAlchemy() class UserState(Enum): """Benutzer-Status fuer Email-Verifizierung und Aktivitaet""" REGISTERED = 'registered' # Signup abgeschlossen, Email nicht verifiziert VERIFIED = 'verified' # Email verifiziert, Container noch nie genutzt ACTIVE = 'active' # Container mindestens einmal gestartet class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), unique=True, nullable=False) slug = db.Column(db.String(12), unique=True, nullable=False, index=True) container_id = db.Column(db.String(100), nullable=True) container_port = db.Column(db.Integer, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) # Admin-Felder is_admin = db.Column(db.Boolean, default=False, nullable=False) # Sperr-Felder is_blocked = db.Column(db.Boolean, default=False, nullable=False) blocked_at = db.Column(db.DateTime, nullable=True) blocked_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) # Email-Verifizierung und Status state = db.Column(db.String(20), default=UserState.REGISTERED.value, nullable=False) # Aktivitaetstracking last_used = db.Column(db.DateTime, nullable=True) # Beziehung fuer blocked_by blocker = db.relationship('User', remote_side=[id], foreign_keys=[blocked_by]) def to_dict(self): """Konvertiert User zu Dictionary fuer API-Responses""" return { 'id': self.id, 'email': self.email, 'slug': self.slug, 'is_admin': self.is_admin, 'is_blocked': self.is_blocked, 'blocked_at': self.blocked_at.isoformat() if self.blocked_at else None, 'state': self.state, 'last_used': self.last_used.isoformat() if self.last_used else None, 'created_at': self.created_at.isoformat() if self.created_at else None, 'container_id': self.container_id } class MagicLinkToken(db.Model): """Magic Link Tokens für Passwordless Authentication""" __tablename__ = 'magic_link_token' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) token = db.Column(db.String(64), unique=True, nullable=False, index=True) token_type = db.Column(db.String(20), nullable=False) # 'signup' oder 'login' created_at = db.Column(db.DateTime, default=datetime.utcnow) expires_at = db.Column(db.DateTime, nullable=False) used_at = db.Column(db.DateTime, nullable=True) ip_address = db.Column(db.String(45), nullable=True) user = db.relationship('User', backref=db.backref('magic_tokens', lazy=True)) def is_valid(self): """Prüft ob Token noch gültig ist""" if self.used_at is not None: return False # Token bereits verwendet if datetime.utcnow() > self.expires_at: return False # Token abgelaufen return True def mark_as_used(self): """Markiert Token als verwendet""" self.used_at = datetime.utcnow() class AdminTakeoverSession(db.Model): """Protokolliert Admin-Zugriffe auf User-Container (Phase 2)""" id = db.Column(db.Integer, primary_key=True) admin_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) target_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) started_at = db.Column(db.DateTime, default=datetime.utcnow) ended_at = db.Column(db.DateTime, nullable=True) reason = db.Column(db.String(500), nullable=True) admin = db.relationship('User', foreign_keys=[admin_id]) target_user = db.relationship('User', foreign_keys=[target_user_id])