106 lines
4.1 KiB
Python
106 lines
4.1 KiB
Python
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
|