diff --git a/api.php b/api.php
index 0482dc4..19e9b4e 100644
--- a/api.php
+++ b/api.php
@@ -1,470 +1,156 @@
- item.type === currentAdminTab);
+ document.getElementById('admin-count-label').textContent = `${filtered.length} élément(s)`;
-$host = 'localhost';
-$db = 'mon_cinema';
-$user = 'root';
-$pass = '';
-$charset = 'utf8mb4';
-$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
-
-try {
- $pdo = new PDO($dsn, $user, $pass, [
- PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
- PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
- PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
- ]);
-} catch (\PDOException $e) {
- echo json_encode(["error" => "Erreur BDD : " . $e->getMessage()]);
- exit;
+ filtered.forEach(f => {
+ const tr = document.createElement('tr');
+ tr.innerHTML = `
+
|
+ ${f.poster ? ` ` : '-'} |
+ ${f.title} |
+ ${f.year || ''} |
+ ${f.director || ''} |
+ ${currentAdminTab === 'critique' ? (f.rating ? '★'.repeat(f.rating) : '☆') : (f.format || '-')} |
+
+
+
+ | `;
+ tbody.appendChild(tr);
+ });
}
-// ── Chiffrement ──────────────────────────────────────────────────
-function encryptData($data) {
- $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
- $encrypted = openssl_encrypt($data, 'aes-256-cbc', ENCRYPTION_KEY, 0, $iv);
- return base64_encode($encrypted . '::' . $iv);
+// ── ACTIONS CRUD ──
+async function saveFilmForm(e) {
+ e.preventDefault();
+ const payload = {
+ type: currentAdminTab,
+ id: document.getElementById('f-id').value,
+ title: document.getElementById('f-title').value,
+ year: document.getElementById('f-year').value,
+ director: document.getElementById('f-director').value,
+ poster: document.getElementById('f-poster').value,
+ // Champs conditionnels
+ rating: document.getElementById('f-rating').value,
+ review: document.getElementById('f-review').value,
+ streaming: document.getElementById('f-streaming').value,
+ format: document.getElementById('f-format').value,
+ length: document.getElementById('f-length').value,
+ publisher: document.getElementById('f-publisher').value,
+ ean_isbn13: document.getElementById('f-ean').value,
+ number_of_discs: document.getElementById('f-discs').value,
+ aspect_ratio: document.getElementById('f-aspect').value,
+ description: document.getElementById('f-description').value
+ };
+
+ await fetch(`${API_URL}?action=save_film`, {
+ method: 'POST',
+ headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+ closeAdminModal();
+ loadDashboardData();
}
-function decryptData($data) {
- if (!$data) return '';
- list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
- return openssl_decrypt($encrypted_data, 'aes-256-cbc', ENCRYPTION_KEY, 0, $iv);
+async function deleteSingleFilm(id) {
+ if (!confirm('Supprimer cette œuvre ?')) return;
+ await fetch(`${API_URL}?action=delete_film&id=${id}&type=${currentAdminTab}`, {
+ method: 'DELETE',
+ headers: { 'Authorization': localStorage.getItem('token') }
+ });
+ loadDashboardData();
}
-// ── Génération d'ID stable et compatible bigint (PHP 32 et 64 bits) ──
-// Produit un entier sur 9 chiffres (< 2^31) → safe partout
-// crc32() PHP retourne un int signé 32 bits → on force positif avec & 0x7FFFFFFF
-// puis on module pour rester dans une plage confortable sans collision sur ~50k films
-function makeStableId($title, $year) {
- $key = strtolower(trim($title)) . '|' . trim($year);
- $hash = abs(crc32($key)); // abs() = toujours positif
- // Reste dans [100000000, 2099999999] → 9-10 chiffres, bien < BIGINT MAX
- return ($hash % 2000000000) + 100000000;
+// ── MODALES & UI ──
+function switchAdminTab(tabName) {
+ currentAdminTab = tabName;
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
+ document.getElementById(`btn-tab-${tabName}`).classList.add('active');
+
+ // Basculer l'affichage des formulaires
+ document.getElementById('form-critique-fields').style.display = tabName === 'critique' ? 'block' : 'none';
+ document.getElementById('form-videotheque-fields').style.display = tabName === 'videotheque' ? 'block' : 'none';
+
+ renderAdminTable();
}
-// Pour les ajouts manuels : timestamp Unix en secondes (10 chiffres, safe)
-function makeNewId() {
- // On ajoute un suffixe aléatoire sur 3 chiffres pour éviter les doublons
- // si deux films sont ajoutés dans la même seconde
- return intval(time() . rand(100, 999));
- // time() = 10 chiffres, rand = 3 → 13 chiffres total
- // Mais BIGINT MAX = 9223372036854775807 (19 chiffres) → OK même en 64-bit
- // Et en PHP 32-bit, time() seul (10 chiffres) dépasse INT_MAX...
- // Solution : utiliser directement crc32 d'un uuid
+function openAddModal() {
+ document.getElementById('film-form').reset();
+ document.getElementById('f-id').value = '';
+ document.getElementById('admin-modal').classList.add('open');
}
-function makeNewIdSafe() {
- // Génère un ID unique sur 9-10 chiffres safe en 32 ET 64 bits
- $unique = uniqid('', true) . rand(1000, 9999);
- return (abs(crc32($unique)) % 2000000000) + 100000000;
+function openEditModal(id) {
+ const item = allItems.find(x => String(x.id) === String(id));
+ if (!item) return;
+ // Remplissage formulaire...
+ document.getElementById('f-id').value = item.id;
+ document.getElementById('f-title').value = item.title;
+ document.getElementById('admin-modal').classList.add('open');
}
-// ── Conversion note Letterboxd (/5 avec demi-points) → entier 1-5 ──
-function convertRating($rawRating) {
- $raw = floatval($rawRating);
- if ($raw <= 0) return 3; // pas de note → neutre
- // Arrondi au plus proche, clampé entre 1 et 5
- $rounded = (int) round($raw);
- return max(1, min(5, $rounded));
+function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); }
+function openConfigModal() { document.getElementById('config-modal').classList.add('open'); }
+function closeConfigModal() { document.getElementById('config-modal').classList.remove('open'); }
+function openPasswordModal() { document.getElementById('password-modal').classList.add('open'); }
+function closePasswordModal() { document.getElementById('password-modal').classList.remove('open'); }
+
+function logout() {
+ localStorage.removeItem('token');
+ window.location.href = 'login.html';
}
-// ── Authentification ─────────────────────────────────────────────
-function checkAuth($pdo) {
- $stmtCheck = $pdo->query("SELECT COUNT(*) as total FROM users");
- if ($stmtCheck->fetch()['total'] == 0) return true;
-
- $token = '';
- if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
- $token = $_SERVER['HTTP_AUTHORIZATION'];
- } elseif (function_exists('apache_request_headers')) {
- $headers = apache_request_headers();
- if (isset($headers['Authorization'])) $token = $headers['Authorization'];
- }
-
- $expectedToken = md5(ENCRYPTION_KEY . 'session');
- if ($token !== $expectedToken) {
- http_response_code(403);
- echo json_encode(["error" => "Accès interdit. Session expirée."]);
- exit;
- }
+// ── MOT DE PASSE ──
+async function saveNewPassword() {
+ const newPwd = document.getElementById('new-password-input').value;
+ const confirmPwd = document.getElementById('new-password-confirm').value;
+ if (newPwd !== confirmPwd) return alert("Les mots de passe ne correspondent pas.");
+
+ const res = await fetch(`${API_URL}?action=update_password`, {
+ method: 'POST',
+ headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
+ body: JSON.stringify({ new_password: newPwd })
+ });
+ if (res.ok) { alert("Mot de passe mis à jour."); closePasswordModal(); }
}
-// ── Requête TMDB ─────────────────────────────────────────────────
-function fetchTmdb($url) {
- if (function_exists('curl_init')) {
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_TIMEOUT, 8);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_USERAGENT, 'MonCinemaApp/1.0');
- $response = curl_exec($ch);
- curl_close($ch);
- return $response;
- }
- $opts = [
- "http" => ["method" => "GET", "header" => "User-Agent: MonCinemaApp/1.0\r\n", "timeout" => 8],
- "ssl" => ["verify_peer" => false, "verify_peer_name" => false]
- ];
- return @file_get_contents($url, false, stream_context_create($opts));
-}
-
-// ════════════════════════════════════════════════════════════════
-$method = $_SERVER['REQUEST_METHOD'];
-$action = $_GET['action'] ?? '';
-
-switch ($method) {
-
- // ════════════════════════════════════════════════════════════
- case 'GET':
-
- if ($action === 'get_films') {
- $stmtC = $pdo->query("SELECT *, 'critique' AS type FROM critiques ORDER BY created_at DESC");
- $critiques = $stmtC->fetchAll();
- $stmtV = $pdo->query("SELECT *, 'videotheque' AS type FROM videotheque ORDER BY created_at DESC");
- $videotheque = $stmtV->fetchAll();
- echo json_encode(array_merge($critiques, $videotheque), JSON_UNESCAPED_UNICODE);
- }
-
- elseif ($action === 'get_tmdb_key_status') {
- checkAuth($pdo);
- $stmt = $pdo->prepare(
- "SELECT COUNT(*) as total FROM config
- WHERE key_name = 'tmdb_api_key'
- AND key_value IS NOT NULL AND key_value != ''"
- );
- $stmt->execute();
- echo json_encode(["exists" => ($stmt->fetch()['total'] > 0)]);
- }
-
- elseif ($action === 'search_tmdb') {
- checkAuth($pdo);
- $query = $_GET['query'] ?? '';
- $stmt = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'tmdb_api_key'");
- $stmt->execute();
- $res = $stmt->fetch();
- $key = $res ? decryptData($res['key_value']) : '';
- if (!$key) { echo json_encode(["error" => "Clé TMDB non configurée."]); exit; }
- $url = "https://api.themoviedb.org/3/search/movie?api_key={$key}&query=" . urlencode($query) . "&language=fr-FR";
- $response = fetchTmdb($url);
- echo $response ?: json_encode(["results" => []]);
- }
-
- elseif ($action === 'check_security_status') {
- $stmt = $pdo->query("SELECT COUNT(*) as total FROM users");
- echo json_encode(["is_blank" => ($stmt->fetch()['total'] == 0)]);
- }
-
- break;
-
- // ════════════════════════════════════════════════════════════
- case 'POST':
-
- // ── Import CSV Letterboxd ─────────────────────────────
- if ($action === 'import_csv') {
- checkAuth($pdo);
-
- if (!isset($_FILES['csv_file']) || $_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) {
- http_response_code(400);
- echo json_encode(["error" => "Fichier CSV invalide ou manquant."]);
- exit;
- }
-
- // Récupérer la clé TMDB (optionnelle)
- $stmtK = $pdo->prepare("SELECT key_value FROM config WHERE key_name = 'tmdb_api_key'");
- $stmtK->execute();
- $resK = $stmtK->fetch();
- $tmdbKey = $resK ? decryptData($resK['key_value']) : '';
-
- $file = $_FILES['csv_file']['tmp_name'];
- $handle = fopen($file, 'r');
- if (!$handle) {
- http_response_code(500);
- echo json_encode(["error" => "Impossible d'ouvrir le fichier."]);
- exit;
- }
-
- // Gestion BOM UTF-8 (présent ou non)
- $bom = fread($handle, 3);
- if ($bom !== "\xEF\xBB\xBF") rewind($handle);
-
- // Lire l'en-tête
- $header = fgetcsv($handle, 0, ',', '"');
- if (!$header) {
- fclose($handle);
- echo json_encode(["error" => "Fichier CSV vide."]);
- exit;
- }
-
- // Nettoyer les noms de colonnes (espaces, BOM résiduels)
- $header = array_map(function($col) {
- return trim(preg_replace('/[\x00-\x1F\x7F\xEF\xBB\xBF]/u', '', $col));
- }, $header);
-
- $colName = array_search('Name', $header);
- $colYear = array_search('Year', $header);
- $colRating = array_search('Rating', $header);
- $colReview = array_search('Review', $header); // absent dans ratings.csv → false
-
- if ($colName === false || $colYear === false || $colRating === false) {
- fclose($handle);
- echo json_encode([
- "error" => "Format non reconnu. Colonnes attendues : Name, Year, Rating. Colonnes trouvées : " . implode(', ', $header)
- ]);
- exit;
- }
-
- $importedCount = 0;
- $errors = [];
-
- while (($row = fgetcsv($handle, 0, ',', '"')) !== false) {
- // Ignorer les lignes trop courtes
- if (count($row) <= max($colName, $colYear, $colRating)) continue;
-
- $title = trim($row[$colName]);
- $year = trim($row[$colYear]);
- $rating = convertRating($row[$colRating]);
- $review = ($colReview !== false && isset($row[$colReview])) ? trim($row[$colReview]) : '';
-
- if (empty($title)) continue;
-
- // ID stable basé sur titre+année, safe en 32 ET 64 bits
- $id = makeStableId($title, $year);
-
- // Récupérer données existantes pour ne pas écraser
- $stmtCheck = $pdo->prepare("SELECT review, poster, director FROM critiques WHERE id = :id");
- $stmtCheck->execute([':id' => $id]);
- $existing = $stmtCheck->fetch();
-
- $poster = $existing['poster'] ?? '';
- $director = $existing['director'] ?? '';
-
- // Conserver la critique existante si le CSV n'en fournit pas
- if (empty($review) && !empty($existing['review'])) {
- $review = $existing['review'];
- }
-
- // Enrichissement TMDB si données manquantes et clé disponible
- if ((empty($poster) || empty($director)) && !empty($tmdbKey)) {
- $apiUrl = "https://api.themoviedb.org/3/search/movie"
- . "?api_key={$tmdbKey}"
- . "&query=" . urlencode($title)
- . ($year ? "&year={$year}" : '')
- . "&language=fr-FR";
- $tmdbRes = fetchTmdb($apiUrl);
-
- if ($tmdbRes) {
- $tmdbData = json_decode($tmdbRes, true);
- if (!empty($tmdbData['results'][0])) {
- $movieData = $tmdbData['results'][0];
- if (empty($poster) && !empty($movieData['poster_path'])) {
- $poster = "https://image.tmdb.org/t/p/w300" . $movieData['poster_path'];
- }
- if (empty($director) && !empty($movieData['id'])) {
- $creditsRes = fetchTmdb("https://api.themoviedb.org/3/movie/{$movieData['id']}/credits?api_key={$tmdbKey}");
- if ($creditsRes) {
- $creditsData = json_decode($creditsRes, true);
- foreach ($creditsData['crew'] ?? [] as $member) {
- if ($member['job'] === 'Director') {
- $director = $member['name'];
- break;
- }
- }
- }
- }
- }
- }
- }
-
- try {
- $stmt = $pdo->prepare(
- "REPLACE INTO critiques (id, title, year, director, rating, review, poster, streaming)
- VALUES (:id, :title, :year, :director, :rating, :review, :poster, '')"
- );
- $stmt->execute([
- ':id' => $id,
- ':title' => $title,
- ':year' => $year,
- ':director' => $director,
- ':rating' => $rating,
- ':review' => $review,
- ':poster' => $poster,
- ]);
- $importedCount++;
- } catch (\PDOException $e) {
- $errors[] = "'{$title}' : " . $e->getMessage();
- }
- }
-
- fclose($handle);
-
- $result = ["success" => true, "imported" => $importedCount];
- if (!empty($errors)) $result['warnings'] = array_slice($errors, 0, 10);
- echo json_encode($result, JSON_UNESCAPED_UNICODE);
- exit;
- }
-
- // ── Lire le body JSON ─────────────────────────────────
- $data = json_decode(file_get_contents('php://input'), true) ?? [];
-
- // ── Connexion ─────────────────────────────────────────
- if ($action === 'login') {
- $stmtCheck = $pdo->query("SELECT COUNT(*) as total FROM users");
- if ($stmtCheck->fetch()['total'] == 0) {
- echo json_encode([
- "success" => true,
- "token" => md5(ENCRYPTION_KEY . 'session'),
- "blank" => true
- ]);
- exit;
- }
- $stmt = $pdo->prepare("SELECT password_hash FROM users WHERE username = 'admin'");
- $stmt->execute();
- $userRow = $stmt->fetch();
-
- if ($userRow && password_verify($data['password'] ?? '', $userRow['password_hash'])) {
- echo json_encode([
- "success" => true,
- "token" => md5(ENCRYPTION_KEY . 'session'),
- "blank" => false
- ]);
- } else {
- http_response_code(401);
- echo json_encode(["error" => "Mot de passe incorrect."]);
- }
- }
-
- // ── Sauvegarde film ───────────────────────────────────
- elseif ($action === 'save_film') {
- checkAuth($pdo);
- $type = $data['type'] ?? 'critique';
- $id = (!empty($data['id'])) ? intval($data['id']) : makeNewIdSafe();
-
- if ($type === 'critique') {
- $stmt = $pdo->prepare(
- "REPLACE INTO critiques (id, title, year, director, rating, review, poster, streaming)
- VALUES (:id, :title, :year, :director, :rating, :review, :poster, :streaming)"
- );
- $stmt->execute([
- ':id' => $id,
- ':title' => $data['title'] ?? '',
- ':year' => $data['year'] ?? '',
- ':director' => $data['director'] ?? '',
- ':rating' => max(1, min(5, intval($data['rating'] ?? 3))),
- ':review' => $data['review'] ?? '',
- ':poster' => $data['poster'] ?? '',
- ':streaming' => $data['streaming'] ?? '',
- ]);
- } else {
- $stmt = $pdo->prepare(
- "REPLACE INTO videotheque
- (id, title, year, director, poster, format, length, publisher, ean_isbn13, number_of_discs, aspect_ratio, description)
- VALUES (:id, :title, :year, :director, :poster, :format, :length, :publisher, :ean_isbn13, :number_of_discs, :aspect_ratio, :description)"
- );
- $stmt->execute([
- ':id' => $id,
- ':title' => $data['title'] ?? '',
- ':year' => $data['year'] ?? '',
- ':director' => $data['director'] ?? '',
- ':poster' => $data['poster'] ?? '',
- ':format' => $data['format'] ?? '',
- ':length' => !empty($data['length']) ? intval($data['length']) : null,
- ':publisher' => $data['publisher'] ?? '',
- ':ean_isbn13' => $data['ean_isbn13'] ?? '',
- ':number_of_discs' => !empty($data['number_of_discs']) ? intval($data['number_of_discs']) : 1,
- ':aspect_ratio' => $data['aspect_ratio'] ?? '',
- ':description' => $data['description'] ?? '',
- ]);
- }
- echo json_encode(["success" => true]);
- }
-
- // ── Sauvegarde clé TMDB ───────────────────────────────
- elseif ($action === 'save_tmdb_key') {
- checkAuth($pdo);
- $stmt = $pdo->prepare(
- "REPLACE INTO config (key_name, key_value) VALUES ('tmdb_api_key', :val)"
- );
- $stmt->execute([':val' => encryptData($data['tmdb_key'] ?? '')]);
- echo json_encode(["success" => true]);
- }
-
- // ── Init compte admin ─────────────────────────────────
- elseif ($action === 'setup_admin') {
- $pwd = $data['password'] ?? '';
- if (strlen($pwd) < 4) {
- http_response_code(400);
- echo json_encode(["error" => "Mot de passe trop court (min. 4 caractères)."]);
- exit;
- }
- $stmt = $pdo->prepare(
- "REPLACE INTO users (id, username, password_hash) VALUES (1, 'admin', :pass)"
- );
- $stmt->execute([':pass' => password_hash($pwd, PASSWORD_BCRYPT)]);
- echo json_encode(["success" => "Compte admin initialisé."]);
- }
-
- // ── Changement mot de passe ───────────────────────────
- elseif ($action === 'update_password') {
- checkAuth($pdo);
- $newPwd = $data['new_password'] ?? '';
- if (strlen($newPwd) < 4) {
- http_response_code(400);
- echo json_encode(["error" => "Mot de passe trop court (min. 4 caractères)."]);
- exit;
- }
- $stmt = $pdo->prepare(
- "REPLACE INTO users (id, username, password_hash) VALUES (1, 'admin', :pass)"
- );
- $stmt->execute([':pass' => password_hash($newPwd, PASSWORD_BCRYPT)]);
- echo json_encode(["success" => "Mot de passe mis à jour."]);
- }
-
- break;
-
- // ════════════════════════════════════════════════════════════
- case 'DELETE':
-
- if ($action === 'delete_film') {
- checkAuth($pdo);
- $id = $_GET['id'] ?? 0;
- $type = $_GET['type'] ?? 'critique';
- $table = ($type === 'videotheque') ? 'videotheque' : 'critiques';
- $stmt = $pdo->prepare("DELETE FROM {$table} WHERE id = :id");
- $stmt->execute([':id' => $id]);
- echo json_encode(["success" => true]);
- }
-
- elseif ($action === 'delete_multiple_films') {
- checkAuth($pdo);
- $input = json_decode(file_get_contents('php://input'), true) ?? [];
- $ids = $input['ids'] ?? [];
- $type = $_GET['type'] ?? 'critique';
- $table = ($type === 'videotheque') ? 'videotheque' : 'critiques';
-
- if (empty($ids) || !is_array($ids)) {
- http_response_code(400);
- echo json_encode(["error" => "Aucun identifiant fourni."]);
- exit;
- }
-
- $ids = array_map('intval', $ids);
- $placeholders = implode(',', array_fill(0, count($ids), '?'));
- $stmt = $pdo->prepare("DELETE FROM {$table} WHERE id IN ({$placeholders})");
- $stmt->execute($ids);
- echo json_encode(["success" => true, "deleted_count" => $stmt->rowCount()]);
- }
-
- break;
+// ── IMPORT CSV ──
+async function handleCsvUpload(input) {
+ if (!input.files[0]) return;
+ const formData = new FormData();
+ formData.append('csv_file', input.files[0]);
+ await fetch(`${API_URL}?action=import_csv`, {
+ method: 'POST',
+ headers: { 'Authorization': localStorage.getItem('token') },
+ body: formData
+ });
+ loadDashboardData();
}
\ No newline at end of file