from datetime import datetime, timezone from flask_login import UserMixin from flask_sqlalchemy import SQLAlchemy from sqlalchemy.orm import relationship from werkzeug.security import check_password_hash, generate_password_hash user_db = SQLAlchemy() class User(UserMixin, user_db.Model): __tablename__ = "user" id = user_db.Column(user_db.Integer, primary_key=True) username = user_db.Column(user_db.String(80), nullable=False, unique=True) password_hash = user_db.Column(user_db.String(255), nullable=False) is_admin = user_db.Column(user_db.Boolean, nullable=False, default=False) created_at = user_db.Column( user_db.DateTime, default=lambda: datetime.now(timezone.utc) ) def set_password(self, password: str) -> None: self.password_hash = generate_password_hash(password) def check_password(self, password: str) -> bool: return check_password_hash(self.password_hash, password) class Note(user_db.Model): __tablename__ = "note" id = user_db.Column(user_db.Integer, primary_key=True) user_id = user_db.Column( user_db.Integer, user_db.ForeignKey("user.id"), nullable=False, index=True ) title = user_db.Column( user_db.String(200), nullable=False, server_default="Untitled" ) content = user_db.Column(user_db.Text, nullable=False) created_at = user_db.Column( user_db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc) ) updated_at = user_db.Column( user_db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), ) items = relationship( "NoteItem", back_populates="note", cascade="all, delete-orphan", ) @property def ordered_items(self): """Unchecked items first, checked items last; stable within each group.""" return sorted(self.items, key=lambda item: (item.is_done, item.id or 0)) def touch(self) -> None: self.updated_at = datetime.now(timezone.utc) class NoteItem(user_db.Model): __tablename__ = "note_item" id = user_db.Column(user_db.Integer, primary_key=True) note_id = user_db.Column( user_db.Integer, user_db.ForeignKey("note.id", ondelete="CASCADE"), nullable=False, index=True, ) text = user_db.Column(user_db.String(255), nullable=False) is_done = user_db.Column(user_db.Boolean, nullable=False, default=False) created_at = user_db.Column( user_db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc) ) note = relationship("Note", back_populates="items") class Notification(user_db.Model): __tablename__ = "notification" id = user_db.Column(user_db.Integer, primary_key=True) user_id = user_db.Column( user_db.Integer, user_db.ForeignKey("user.id"), nullable=False, index=True ) message = user_db.Column(user_db.String(500), nullable=False) is_read = user_db.Column(user_db.Boolean, nullable=False, default=False) created_at = user_db.Column( user_db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc) ) read_at = user_db.Column(user_db.DateTime, nullable=True) @property def status(self) -> str: return "read" if self.is_read else "new" def mark_read(self) -> None: if not self.is_read: self.is_read = True self.read_at = datetime.now(timezone.utc) class ClipboardFile(user_db.Model): __tablename__ = "clipboard_file" id = user_db.Column(user_db.Integer, primary_key=True) user_id = user_db.Column( user_db.Integer, user_db.ForeignKey("user.id"), nullable=False, index=True ) original_name = user_db.Column(user_db.String(255), nullable=False) stored_name = user_db.Column(user_db.String(64), nullable=False, unique=True) size = user_db.Column(user_db.BigInteger, nullable=False) content_type = user_db.Column(user_db.String(255), nullable=True) created_at = user_db.Column( user_db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc) )