import uuid from datetime import datetime, timezone from flask_login import UserMixin from werkzeug.security import check_password_hash, generate_password_hash from app import db class User(UserMixin, db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), 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) is_admin = db.Column(db.Boolean, nullable=False, default=False) is_active = db.Column(db.Boolean, nullable=False, default=True) created_at = db.Column( db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), ) photos = db.relationship("Photo", backref="owner", lazy="dynamic") folders = db.relationship("Folder", backref="owner", lazy="dynamic") 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) @property def photo_count(self): return self.photos.count() @property def total_size(self): from sqlalchemy import func result = db.session.query(func.coalesce(func.sum(Photo.file_size), 0)).filter( Photo.user_id == self.id ).scalar() return int(result or 0) class Folder(db.Model): __tablename__ = "folders" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(120), nullable=False) owner_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True) share_token = db.Column(db.String(64), unique=True, nullable=False, index=True) is_private = db.Column(db.Boolean, nullable=False, default=True) password_hash = db.Column(db.String(256), nullable=True) created_at = db.Column( db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), ) photos = db.relationship("Photo", backref="folder", lazy="dynamic") members = db.relationship("FolderMember", backref="folder", lazy="dynamic", cascade="all, delete-orphan") invites = db.relationship("FolderInvite", backref="folder", lazy="dynamic", cascade="all, delete-orphan") def __init__(self, **kwargs): super().__init__(**kwargs) if not self.share_token: self.share_token = uuid.uuid4().hex def set_access_password(self, password): if password: self.password_hash = generate_password_hash(password) else: self.password_hash = None def check_access_password(self, password): if not self.password_hash: return True return check_password_hash(self.password_hash, password) @property def has_password(self): return bool(self.password_hash) @property def photo_count(self): return self.photos.count() def regenerate_share_token(self): self.share_token = uuid.uuid4().hex class FolderMember(db.Model): __tablename__ = "folder_members" id = db.Column(db.Integer, primary_key=True) folder_id = db.Column(db.Integer, db.ForeignKey("folders.id"), nullable=False, index=True) user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True) role = db.Column(db.String(20), nullable=False, default="viewer") added_at = db.Column( db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), ) added_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True) user = db.relationship("User", foreign_keys=[user_id]) added_by = db.relationship("User", foreign_keys=[added_by_id]) __table_args__ = (db.UniqueConstraint("folder_id", "user_id", name="uq_folder_member"),) class FolderInvite(db.Model): __tablename__ = "folder_invites" id = db.Column(db.Integer, primary_key=True) folder_id = db.Column(db.Integer, db.ForeignKey("folders.id"), nullable=False, index=True) email = db.Column(db.String(120), nullable=False, index=True) role = db.Column(db.String(20), nullable=False, default="viewer") invited_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) created_at = db.Column( db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), ) invited_by = db.relationship("User", foreign_keys=[invited_by_id]) __table_args__ = (db.UniqueConstraint("folder_id", "email", name="uq_folder_invite"),) class Photo(db.Model): __tablename__ = "photos" id = db.Column(db.Integer, primary_key=True) filename = db.Column(db.String(255), nullable=False) original_name = db.Column(db.String(255), nullable=False) file_size = db.Column(db.Integer, nullable=False, default=0) mime_type = db.Column(db.String(100), nullable=False, default="image/jpeg") user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True, index=True) folder_id = db.Column(db.Integer, db.ForeignKey("folders.id"), nullable=True, index=True) created_at = db.Column( db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), ) @property def url(self): return f"/uploads/{self.filename}" @property def size_human(self): size = self.file_size for unit in ("Б", "КБ", "МБ", "ГБ"): if size < 1024: return f"{size:.0f} {unit}" if unit == "Б" else f"{size:.1f} {unit}" size /= 1024 return f"{size:.1f} ТБ"