from datetime import datetime from app import db from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from sqlalchemy.ext.associationproxy import association_proxy # User roles class Role: MEMBER = 'member' MODERATOR = 'moderator' ADMIN = 'admin' # Association table for topics and tags topic_tag = db.Table('topic_tag', db.Column('topic_id', db.Integer, db.ForeignKey('topic.id'), primary_key=True), db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True) ) # User model class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, nullable=False, index=True) email = db.Column(db.String(120), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(256), nullable=False) role = db.Column(db.String(20), nullable=False, default=Role.MEMBER) avatar = db.Column(db.String(120), nullable=True, default='default.png') signature = db.Column(db.String(200), nullable=True) location = db.Column(db.String(100), nullable=True) website = db.Column(db.String(120), nullable=True) bio = db.Column(db.Text, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) last_seen = db.Column(db.DateTime, default=datetime.utcnow) is_active = db.Column(db.Boolean, default=True) is_banned = db.Column(db.Boolean, default=False) ban_reason = db.Column(db.Text, nullable=True) # Relationships topics = db.relationship('Topic', backref='author', lazy='dynamic') posts = db.relationship('Post', backref='author', lazy='dynamic', foreign_keys='Post.author_id') reactions = db.relationship('Reaction', backref='user', lazy='dynamic') reports = db.relationship('Report', backref='reporter', lazy='dynamic', foreign_keys='Report.reporter_id') 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) def is_admin(self): return self.role == Role.ADMIN def is_moderator(self): return self.role == Role.MODERATOR or self.role == Role.ADMIN def update_last_seen(self): self.last_seen = datetime.utcnow() db.session.commit() def __repr__(self): return f'' # Category model class Category(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) description = db.Column(db.Text, nullable=True) order = db.Column(db.Integer, default=0) # Relationships topics = db.relationship('Topic', backref='category', lazy='dynamic') def topic_count(self): return self.topics.count() def post_count(self): count = 0 for topic in self.topics: count += topic.posts.count() return count def __repr__(self): return f'' # Topic model class Topic(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) views = db.Column(db.Integer, default=0) is_locked = db.Column(db.Boolean, default=False) is_pinned = db.Column(db.Boolean, default=False) # Foreign keys category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False) author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # Relationships posts = db.relationship('Post', backref='topic', lazy='dynamic', cascade='all, delete-orphan') tags = db.relationship('Tag', secondary=topic_tag, backref=db.backref('topics', lazy='dynamic')) reports = db.relationship('Report', backref='topic', lazy='dynamic', primaryjoin="and_(Report.topic_id==Topic.id, Report.post_id==None)") def reply_count(self): return self.posts.count() - 1 # Subtract first post def last_post(self): return self.posts.order_by(Post.created_at.desc()).first() def increment_view(self): self.views += 1 db.session.commit() def __repr__(self): return f'' # Post model class Post(db.Model): id = db.Column(db.Integer, primary_key=True) content = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) edited_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) # Foreign keys topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), nullable=False) author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # Relationships reactions = db.relationship('Reaction', backref='post', lazy='dynamic', cascade='all, delete-orphan') reports = db.relationship('Report', backref='post', lazy='dynamic', primaryjoin="Report.post_id==Post.id") edited_by = db.relationship('User', foreign_keys=[edited_by_id]) def get_reaction_count(self, reaction_type=None): if reaction_type: return self.reactions.filter_by(reaction_type=reaction_type).count() return self.reactions.count() def __repr__(self): return f'' # Tag model class Tag(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False, unique=True) def __repr__(self): return f'' # Reaction model class Reaction(db.Model): id = db.Column(db.Integer, primary_key=True) reaction_type = db.Column(db.String(20), nullable=False, default='like') created_at = db.Column(db.DateTime, default=datetime.utcnow) # Foreign keys user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False) # Enforce unique reaction per user per post __table_args__ = ( db.UniqueConstraint('user_id', 'post_id', name='_user_post_reaction_uc'), ) def __repr__(self): return f'' # Report model class Report(db.Model): id = db.Column(db.Integer, primary_key=True) reason = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) is_resolved = db.Column(db.Boolean, default=False) resolved_at = db.Column(db.DateTime, nullable=True) resolved_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) # Foreign keys reporter_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), nullable=True) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=True) # Relationships resolved_by = db.relationship('User', foreign_keys=[resolved_by_id]) def __repr__(self): return f''