from datetime import datetime from app import db class ExtraFile(db.Model): """Model for extra files attached to sets (BrickLink XML, Stud.io, box art, etc).""" __tablename__ = 'extra_files' id = db.Column(db.Integer, primary_key=True) set_id = db.Column(db.Integer, db.ForeignKey('sets.id', ondelete='CASCADE'), nullable=False) # File information file_name = db.Column(db.String(255), nullable=False) original_filename = db.Column(db.String(255), nullable=False) # Original name before hashing file_path = db.Column(db.String(500), nullable=False) file_type = db.Column(db.String(50), nullable=False) # Extension file_size = db.Column(db.Integer, nullable=False) # Size in bytes # Metadata description = db.Column(db.Text) category = db.Column(db.String(50)) # 'bricklink', 'studio', 'box_art', 'document', 'photo', 'other' # Tracking uploaded_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) uploaded_by = db.Column(db.Integer, db.ForeignKey('users.id')) # Relationships lego_set = db.relationship('Set', back_populates='extra_files') uploader = db.relationship('User', backref='uploaded_files') def __repr__(self): return f'' @property def file_size_formatted(self): """Return human-readable file size.""" size = self.file_size for unit in ['B', 'KB', 'MB', 'GB']: if size < 1024.0: return f"{size:.1f} {unit}" size /= 1024.0 return f"{size:.1f} TB" @property def file_icon(self): """Return Bootstrap icon class based on file type.""" icon_map = { # Images 'jpg': 'file-image', 'jpeg': 'file-image', 'png': 'file-image', 'gif': 'file-image', 'webp': 'file-image', 'bmp': 'file-image', # Documents 'pdf': 'file-pdf', 'doc': 'file-word', 'docx': 'file-word', 'txt': 'file-text', 'rtf': 'file-text', # Spreadsheets 'xls': 'file-excel', 'xlsx': 'file-excel', 'csv': 'file-spreadsheet', # Data files 'xml': 'file-code', 'json': 'file-code', # 3D/CAD files 'ldr': 'box-seam', # LDraw 'mpd': 'box-seam', # LDraw 'io': 'box-seam', # Stud.io 'lxf': 'box-seam', # LEGO Digital Designer 'lxfml': 'box-seam', # Archives 'zip': 'file-zip', 'rar': 'file-zip', '7z': 'file-zip', 'tar': 'file-zip', 'gz': 'file-zip', # Other 'default': 'file-earmark' } return icon_map.get(self.file_type.lower(), icon_map['default']) @property def is_image(self): """Check if file is an image.""" image_types = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'] return self.file_type.lower() in image_types @property def can_preview(self): """Check if file can be previewed in browser.""" preview_types = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'txt'] return self.file_type.lower() in preview_types def to_dict(self): """Convert to dictionary for JSON responses.""" return { 'id': self.id, 'set_id': self.set_id, 'file_name': self.file_name, 'original_filename': self.original_filename, 'file_path': self.file_path.replace('\\', '/'), 'file_type': self.file_type, 'file_size': self.file_size, 'file_size_formatted': self.file_size_formatted, 'description': self.description, 'category': self.category, 'uploaded_at': self.uploaded_at.isoformat(), 'is_image': self.is_image, 'can_preview': self.can_preview, 'file_icon': self.file_icon, 'download_url': f'/extra-files/download/{self.id}' }