269 lines
8.7 KiB
Python
269 lines
8.7 KiB
Python
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/<movie_name>/<movie_year>', 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<name>.+?)\s*\((?P<year>\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
|