from flask_sqlalchemy import SQLAlchemy from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash db = SQLAlchemy() class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password_hash = db.Column(db.String(128)) is_admin = db.Column(db.Boolean, default=False) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) class NodeImage(db.Model): id = db.Column(db.Integer, primary_key=True) node_id = db.Column(db.Integer, db.ForeignKey('node.id'), nullable=False) image_url = db.Column(db.String(500), nullable=False) caption = db.Column(db.String(200), nullable=True) display_order = db.Column(db.Integer, default=0) # Relationship back to the node node = db.relationship('Node', backref=db.backref('images', cascade='all, delete-orphan')) class Node(db.Model): id = db.Column(db.Integer, primary_key=True) question = db.Column(db.String(500), nullable=False) content = db.Column(db.Text, nullable=True) is_terminal = db.Column(db.Boolean, default=False) # Keep single image_url for backward compatibility image_url = db.Column(db.String(500), nullable=True) youtube_url = db.Column(db.String(500), nullable=True) # Relationships yes_node_id = db.Column(db.Integer, db.ForeignKey('node.id'), nullable=True) no_node_id = db.Column(db.Integer, db.ForeignKey('node.id'), nullable=True) yes_node = db.relationship('Node', foreign_keys=[yes_node_id], remote_side=[id], backref='yes_parent', uselist=False) no_node = db.relationship('Node', foreign_keys=[no_node_id], remote_side=[id], backref='no_parent', uselist=False) @property def youtube_embed_url(self): """Convert YouTube URL to embed URL""" if not self.youtube_url: return None import re from urllib.parse import urlparse, parse_qs # Extract video ID from various YouTube URL formats video_id = None # youtube.com/watch?v=VIDEO_ID format if 'youtube.com/watch' in self.youtube_url: parsed_url = urlparse(self.youtube_url) video_id = parse_qs(parsed_url.query).get('v', [None])[0] # youtu.be/VIDEO_ID format elif 'youtu.be/' in self.youtube_url: video_id = self.youtube_url.split('youtu.be/')[1].split('?')[0] if video_id: return f"https://www.youtube.com/embed/{video_id}" return None class Product(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), nullable=False) thumbnail = db.Column(db.String(200)) # URL or file path to the thumbnail image description = db.Column(db.Text) # Add this line for product description root_node_id = db.Column(db.Integer, db.ForeignKey('node.id')) root_node = db.relationship("Node", foreign_keys=[root_node_id]) class SiteConfig(db.Model): id = db.Column(db.Integer, primary_key=True) key = db.Column(db.String(64), unique=True, nullable=False) value = db.Column(db.String(255), nullable=True) description = db.Column(db.String(255), nullable=True) @classmethod def get_setting(cls, key, default=None): setting = cls.query.filter_by(key=key).first() if setting: return setting.value return default @classmethod def set_setting(cls, key, value, description=None): setting = cls.query.filter_by(key=key).first() if setting: setting.value = value if description: setting.description = description else: setting = cls(key=key, value=value, description=description) db.session.add(setting) db.session.commit() return setting