import io import math import os import zipfile import requests import uuid from flask import Blueprint, jsonify, request, session, send_file from werkzeug.utils import secure_filename from src.services.tmdb_service import TMDBService from src.services.image_service import ImageService import pandas as pd from functools import lru_cache api_bp = Blueprint('api', __name__) tmdb_service = TMDBService() image_service = ImageService() ALLOWED_EXTENSIONS = {'csv'} user_csvs = {} @lru_cache(maxsize=1000) def get_poster_url(movie_name, movie_year): movie = tmdb_service.search_movie(movie_name, movie_year) if movie: return movie['poster_path'] return None @api_bp.route('/movies', methods=['GET']) def get_movies(): user_id = session.get('user_id') # Récupérer l'ID de l'utilisateur de la session if user_id not in user_csvs: return jsonify({'movies': [], 'message': 'No movies found. Please upload a CSV file.'}), 200 # Lire le DataFrame à partir du cache csv_content = user_csvs[user_id] try: # Si csv_content est une chaîne, il n'est pas nécessaire de décoder if isinstance(csv_content, bytes): df = pd.read_csv(io.StringIO(csv_content.decode('utf-8'))) else: df = pd.read_csv(io.StringIO(csv_content)) # Reste de votre code pour la pagination et le traitement... page = int(request.args.get('page', 1)) limit = int(request.args.get('limit', 10)) df = df.iloc[::-1].reset_index(drop=True) total_movies = len(df) total_pages = math.ceil(total_movies / limit) start_index = (page - 1) * limit end_index = start_index + limit df_page = df.iloc[start_index:end_index] movies = df_page.to_dict('records') movies_with_posters = [ { 'id': start_index + i + 1, 'Watched Date': movie['Watched Date'], 'Name': movie['Name'], 'Year': movie['Year'], 'Poster': get_poster_url(movie['Name'], movie['Year']), } for i, movie in enumerate(movies) ] return jsonify({ 'movies': movies_with_posters, 'total': total_movies, 'page': page, 'total_pages': total_pages }) except Exception as e: print(f"Error in get_movies: {str(e)}") return jsonify({'error': 'An error occurred while fetching movies'}), 500 @api_bp.route('/posters//', methods=['GET']) def get_movie_posters(movie_name, movie_year): tmdb_service = TMDBService() movie = tmdb_service.search_movie(movie_name, movie_year) if movie: posters = tmdb_service.get_movie_posters(movie['id']) return jsonify({'posters': posters}) else: return jsonify({'error': 'Movie not found'}), 404 def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @api_bp.route('/upload-csv', methods=['POST']) def upload_csv(): user_id = session.get('user_id') if not user_id: user_id = str(uuid.uuid4()) session['user_id'] = user_id if 'file' not in request.files: return jsonify({'error': 'No file part'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'No selected file'}), 400 if file and allowed_file(file.filename): content = file.read() user_csvs[user_id] = content try: df = pd.read_csv(io.StringIO(content.decode('utf-8'))) df = df.rename(columns={ 'Date': 'Watched Date', 'Name': 'Name', 'Year': 'Year' }) user_csvs[user_id] = df.to_csv(index=False) return jsonify({'success': True, 'message': 'File uploaded and processed successfully'}), 200 except Exception as e: return jsonify({'error': f'Error processing file: {str(e)}'}), 500 return jsonify({'error': 'File type not allowed'}), 400 @api_bp.route('/check-csv', methods=['GET']) def check_csv(): user_id = session.get('user_id') try: if user_id in user_csvs: return jsonify({'fileExists': True}), 200 else: return jsonify({'fileExists': False}), 200 except Exception as e: print(f"Error in check_csv: {str(e)}") return jsonify({'error': 'An error occurred while checking the CSV file'}), 500 @api_bp.route('/delete-csv', methods=['DELETE']) def delete_csv(): user_id = session.get('user_id') try: if user_id in user_csvs: del user_csvs[user_id] return jsonify({'success': True, 'message': 'CSV file deleted successfully'}), 200 else: return jsonify({'error': 'CSV file not found'}), 404 except Exception as e: print(f"Error in delete_csv: {str(e)}") return jsonify({'error': 'An error occurred while deleting the CSV file'}), 500 @api_bp.route('/fetch-diary', methods=['POST']) def fetch_diary_from_username(): """ Récupère le diary via l'API publique (api.hugo-pierret.be), transforme en CSV et le stocke dans user_csvs[user_id] pour que le front continue à utiliser le même flux que l'upload. """ try: data = request.get_json() or {} username = data.get('username') if not username: return jsonify({'error': 'username required'}), 400 user_id = session.get('user_id') if not user_id: user_id = str(uuid.uuid4()) session['user_id'] = user_id api_url = f"https://api.hugo-pierret.be/letterboxd/diary?username={username}" resp = requests.get(api_url) if resp.status_code != 200: return jsonify({'error': 'Failed to fetch diary from upstream API'}), 502 payload = resp.json() entries = payload.get('entries', []) if not entries: return jsonify({'error': 'No entries returned by upstream API'}), 404 rows = [] import re for e in entries: title = e.get('title') or "" date = e.get('date') or "" m = re.match(r"^(?P.+?)\s*\((?P\d{4})\)\s*$", title) if m: name = m.group('name').strip() year = m.group('year') else: year_match = re.search(r"(\d{4})", title) year = year_match.group(1) if year_match else "" name = re.sub(r"\(\d{4}\)", "", title).strip() rows.append({ "Watched Date": date, "Name": name, "Year": year }) import pandas as pd, io rows.reverse() df = pd.DataFrame(rows) csv_str = df.to_csv(index=False) user_csvs[user_id] = csv_str return jsonify({'success': True, 'message': 'Diary fetched and stored', 'total': len(rows)}), 200 except Exception as e: print("Error in fetch_diary_from_username:", str(e)) return jsonify({'error': 'An internal error occurred'}), 500 @api_bp.route('/download-posters', methods=['POST']) def download_posters(): try: data = request.get_json() selected_posters = data.get('posters', []) if not selected_posters: return jsonify({'error': 'No posters selected'}), 400 zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: # Télécharger chaque poster for poster in selected_posters: movie_id = poster['movieId'] poster_id = poster['posterId'] watched_date = poster.get('watchedDate', 'unknown-date') poster_url = f"https://image.tmdb.org/t/p/original{poster_id}" try: response = requests.get(poster_url) if response.status_code == 200: file_name = f"{watched_date}_{movie_id}.jpg" zip_file.writestr(file_name, response.content) else: print(f"Failed to download poster for movie ID {movie_id}") except Exception as e: print(f"Error downloading poster for movie ID {movie_id}: {str(e)}") # Retourner le fichier ZIP zip_buffer.seek(0) return send_file( zip_buffer, mimetype='application/zip', as_attachment=True, download_name='posters.zip' ) except Exception as e: print(f"Error in download_posters: {str(e)}") return jsonify({'error': 'An error occurred while fetching movie posters'}), 500