from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app from flask_login import login_required, current_user import os from app import db from app.models.set import Set from app.models.instruction import Instruction from app.services.brickset_api import BricksetAPI from app.services.file_handler import FileHandler sets_bp = Blueprint('sets', __name__, url_prefix='/sets') @sets_bp.route('/debug/') @login_required def debug_set_image(set_id): """Debug route to check image paths.""" lego_set = Set.query.get_or_404(set_id) debug_info = { 'set_number': lego_set.set_number, 'set_name': lego_set.set_name, 'cover_image_db': lego_set.cover_image, 'image_url_db': lego_set.image_url, 'get_image_result': lego_set.get_image(), 'cover_image_exists': False, 'cover_image_full_path': None } # Check if file actually exists if lego_set.cover_image: from flask import current_app full_path = os.path.join(current_app.config['UPLOAD_FOLDER'], lego_set.cover_image) debug_info['cover_image_full_path'] = full_path debug_info['cover_image_exists'] = os.path.exists(full_path) return jsonify(debug_info) @sets_bp.route('/') @login_required def list_sets(): """List all LEGO sets with sorting and filtering.""" page = request.args.get('page', 1, type=int) sort_by = request.args.get('sort', 'set_number') theme_filter = request.args.get('theme', '') year_filter = request.args.get('year', type=int) search_query = request.args.get('q', '') # Build query query = Set.query # Apply filters if theme_filter: query = query.filter(Set.theme == theme_filter) if year_filter: query = query.filter(Set.year_released == year_filter) if search_query: query = query.filter( db.or_( Set.set_name.ilike(f'%{search_query}%'), Set.set_number.ilike(f'%{search_query}%') ) ) # Apply sorting if sort_by == 'set_number': query = query.order_by(Set.set_number) elif sort_by == 'name': query = query.order_by(Set.set_name) elif sort_by == 'theme': query = query.order_by(Set.theme, Set.set_number) elif sort_by == 'year': query = query.order_by(Set.year_released.desc(), Set.set_number) elif sort_by == 'newest': query = query.order_by(Set.created_at.desc()) # Paginate from flask import current_app per_page = current_app.config.get('SETS_PER_PAGE', 20) pagination = query.paginate(page=page, per_page=per_page, error_out=False) # Get unique themes and years for filters themes = db.session.query(Set.theme).distinct().order_by(Set.theme).all() themes = [t[0] for t in themes] years = db.session.query(Set.year_released).distinct().order_by(Set.year_released.desc()).all() years = [y[0] for y in years] return render_template('sets/list.html', sets=pagination.items, pagination=pagination, themes=themes, years=years, current_theme=theme_filter, current_year=year_filter, current_sort=sort_by, search_query=search_query) @sets_bp.route('/') @login_required def view_set(set_id): """View detailed information about a specific set.""" lego_set = Set.query.get_or_404(set_id) # Get instructions grouped by type pdf_instructions = lego_set.pdf_instructions image_instructions = lego_set.image_instructions return render_template('sets/detail.html', set=lego_set, pdf_instructions=pdf_instructions, image_instructions=image_instructions) @sets_bp.route('/add', methods=['GET', 'POST']) @login_required def add_set(): """Add a new LEGO set or MOC.""" if request.method == 'POST': set_number = request.form.get('set_number', '').strip() set_name = request.form.get('set_name', '').strip() theme = request.form.get('theme', '').strip() year_released = request.form.get('year_released', type=int) piece_count = request.form.get('piece_count', type=int) image_url = request.form.get('image_url', '').strip() # MOC fields is_moc = request.form.get('is_moc') == 'on' moc_designer = request.form.get('moc_designer', '').strip() if is_moc else None moc_description = request.form.get('moc_description', '').strip() if is_moc else None # Validation if not all([set_number, set_name, theme, year_released]): flash('Set number, name, theme, and year are required.', 'danger') return render_template('sets/add.html') # Check if set already exists if Set.query.filter_by(set_number=set_number).first(): flash(f'Set {set_number} already exists in the database.', 'warning') return redirect(url_for('sets.list_sets')) # Handle cover image upload cover_image_path = None if 'cover_image' in request.files: file = request.files['cover_image'] if file and file.filename and FileHandler.allowed_file(file.filename): try: cover_image_path, _ = FileHandler.save_cover_image(file, set_number) except Exception as e: flash(f'Error uploading cover image: {str(e)}', 'warning') # Create new set new_set = Set( set_number=set_number, set_name=set_name, theme=theme, year_released=year_released, piece_count=piece_count, image_url=image_url, cover_image=cover_image_path, is_moc=is_moc, moc_designer=moc_designer, moc_description=moc_description, user_id=current_user.id ) db.session.add(new_set) db.session.commit() set_type = "MOC" if is_moc else "Set" flash(f'{set_type} {set_number}: {set_name} added successfully!', 'success') return redirect(url_for('sets.view_set', set_id=new_set.id)) return render_template('sets/add.html') @sets_bp.route('//edit', methods=['GET', 'POST']) @login_required def edit_set(set_id): """Edit an existing LEGO set or MOC.""" lego_set = Set.query.get_or_404(set_id) if request.method == 'POST': lego_set.set_name = request.form.get('set_name', '').strip() lego_set.theme = request.form.get('theme', '').strip() lego_set.year_released = request.form.get('year_released', type=int) lego_set.piece_count = request.form.get('piece_count', type=int) lego_set.image_url = request.form.get('image_url', '').strip() # Update MOC fields lego_set.is_moc = request.form.get('is_moc') == 'on' lego_set.moc_designer = request.form.get('moc_designer', '').strip() if lego_set.is_moc else None lego_set.moc_description = request.form.get('moc_description', '').strip() if lego_set.is_moc else None # Handle cover image upload if 'cover_image' in request.files: file = request.files['cover_image'] if file and file.filename and FileHandler.allowed_file(file.filename): try: # Delete old cover image if exists if lego_set.cover_image: old_path = os.path.join(current_app.config['UPLOAD_FOLDER'], lego_set.cover_image) if os.path.exists(old_path): os.remove(old_path) # Save new cover image cover_image_path, _ = FileHandler.save_cover_image(file, lego_set.set_number) lego_set.cover_image = cover_image_path except Exception as e: flash(f'Error uploading cover image: {str(e)}', 'warning') # Option to remove cover image if request.form.get('remove_cover_image') == 'on': if lego_set.cover_image: old_path = os.path.join(current_app.config['UPLOAD_FOLDER'], lego_set.cover_image) if os.path.exists(old_path): os.remove(old_path) lego_set.cover_image = None db.session.commit() flash('Set updated successfully!', 'success') return redirect(url_for('sets.view_set', set_id=set_id)) return render_template('sets/edit.html', set=lego_set) @sets_bp.route('//delete', methods=['POST']) @login_required def delete_set(set_id): """Delete a LEGO set and all its instructions.""" lego_set = Set.query.get_or_404(set_id) # Delete associated files from app.services.file_handler import FileHandler for instruction in lego_set.instructions: FileHandler.delete_file(instruction.file_path) db.session.delete(lego_set) db.session.commit() flash(f'Set {lego_set.set_number} deleted successfully.', 'success') return redirect(url_for('sets.list_sets')) @sets_bp.route('/search-brickset') @login_required def search_brickset(): """Search for sets using Brickset API.""" query = request.args.get('q', '').strip() if not query: return jsonify({'error': 'Search query is required'}), 400 api = BricksetAPI() # Try to search by set number first, then by name results = api.search_sets(set_number=query) if not results: results = api.search_sets(query=query) # Format results for JSON response formatted_results = [] for result in results[:10]: # Limit to 10 results formatted_results.append({ 'setNumber': result.get('number'), 'name': result.get('name'), 'theme': result.get('theme'), 'year': result.get('year'), 'pieces': result.get('pieces'), 'imageUrl': result.get('image', {}).get('imageURL') if result.get('image') else None }) return jsonify(formatted_results)