Questions now have a created_by field linking to the user who created them. Users only see questions they own or that have been shared with them. Includes share dialog, user search, bulk sharing, and export/import respects ownership. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
109 lines
3.2 KiB
Python
109 lines
3.2 KiB
Python
"""Routes for exporting and importing trivia questions"""
|
|
import os
|
|
import tempfile
|
|
from flask import Blueprint, jsonify, Response, request, g
|
|
from werkzeug.utils import secure_filename
|
|
|
|
from backend.auth.middleware import require_auth
|
|
from backend.services.export_import_service import (
|
|
export_questions_to_zip,
|
|
import_questions_from_zip
|
|
)
|
|
|
|
bp = Blueprint('export_import', __name__, url_prefix='/api/admin')
|
|
|
|
|
|
@bp.route('/export', methods=['POST'])
|
|
@require_auth
|
|
def export_data():
|
|
"""
|
|
Export all questions and categories to a ZIP file.
|
|
|
|
Returns:
|
|
ZIP file containing manifest.json and all media files
|
|
"""
|
|
try:
|
|
# Generate export ZIP (only questions visible to current user)
|
|
zip_bytes, zip_filename = export_questions_to_zip(user_id=g.current_user.id)
|
|
|
|
# Create response with ZIP file
|
|
response = Response(
|
|
zip_bytes,
|
|
mimetype='application/zip',
|
|
headers={
|
|
'Content-Disposition': f'attachment; filename={zip_filename}',
|
|
'Content-Length': str(len(zip_bytes))
|
|
}
|
|
)
|
|
|
|
return response
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': f'Export failed: {str(e)}'}), 500
|
|
|
|
|
|
@bp.route('/import', methods=['POST'])
|
|
@require_auth
|
|
def import_data():
|
|
"""
|
|
Import questions and categories from a ZIP file.
|
|
|
|
Expects:
|
|
multipart/form-data with 'file' key containing ZIP file
|
|
|
|
Returns:
|
|
JSON with import summary
|
|
"""
|
|
# Check if file was uploaded
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'No file provided'}), 400
|
|
|
|
file = request.files['file']
|
|
|
|
# Check if filename is present
|
|
if file.filename == '':
|
|
return jsonify({'error': 'No file selected'}), 400
|
|
|
|
# Check if file is a ZIP
|
|
if not file.filename.lower().endswith('.zip'):
|
|
return jsonify({'error': 'File must be a ZIP archive'}), 400
|
|
|
|
temp_path = None
|
|
|
|
try:
|
|
# Save uploaded file to temporary location
|
|
temp_dir = tempfile.mkdtemp()
|
|
temp_path = os.path.join(temp_dir, secure_filename(file.filename))
|
|
|
|
print(f"Saving uploaded file to: {temp_path}")
|
|
file.save(temp_path)
|
|
|
|
# Verify file was saved
|
|
file_size = os.path.getsize(temp_path)
|
|
print(f"Saved file size: {file_size} bytes")
|
|
|
|
# Import from ZIP (set ownership to current user)
|
|
result = import_questions_from_zip(temp_path, user_id=g.current_user.id)
|
|
|
|
return jsonify(result), 200
|
|
|
|
except ValueError as e:
|
|
# Validation errors
|
|
return jsonify({'error': str(e)}), 400
|
|
|
|
except Exception as e:
|
|
# Other errors
|
|
return jsonify({'error': f'Import failed: {str(e)}'}), 500
|
|
|
|
finally:
|
|
# Clean up uploaded file
|
|
if temp_path and os.path.exists(temp_path):
|
|
try:
|
|
os.remove(temp_path)
|
|
# Remove temp directory if empty
|
|
temp_dir = os.path.dirname(temp_path)
|
|
if os.path.exists(temp_dir) and not os.listdir(temp_dir):
|
|
os.rmdir(temp_dir)
|
|
except Exception as e:
|
|
print(f"Warning: Failed to clean up temp file {temp_path}: {e}")
|