From 3abc6f637130868b2c75f1c9cd189506677256a6 Mon Sep 17 00:00:00 2001 From: NADAL Jean-Baptiste Date: Wed, 18 Feb 2026 14:04:34 +0100 Subject: [PATCH] [WIP] Skeleton of the admin part. --- api/index.php | 41 +- api/lib/ScoreScanner.php | 45 ++- api/php-upload.ini | 2 + partitions/src/lib/api.ts | 34 ++ partitions/src/routes/+layout.svelte | 7 + partitions/src/routes/admin/+page.svelte | 474 +++++++++++++++++++++++ 6 files changed, 592 insertions(+), 11 deletions(-) create mode 100644 api/php-upload.ini create mode 100644 partitions/src/routes/admin/+page.svelte diff --git a/api/index.php b/api/index.php index 5b1524e..7e1a07a 100644 --- a/api/index.php +++ b/api/index.php @@ -1,7 +1,11 @@ listScores(); + $maxId = 0; + foreach ($scores as $s) { + $num = intval($s['id']); + if ($num > $maxId) $maxId = $maxId; + } + // Find highest numeric ID + foreach ($scores as $s) { + $num = intval($s['id']); + if ($num > $maxId) $maxId = $num; + } + $id = strval($maxId + 1); + // Pad with zeros to 3 digits if needed + if (strlen($id) < 3) { + $id = str_pad($id, 3, '0', STR_PAD_LEFT); + } + } + + if (empty($name)) { http_response_code(400); echo json_encode(['error' => 'ID and name required']); exit; @@ -227,9 +257,12 @@ if (preg_match('#^admin/scores/(\d+)/upload$#', $path, $matches) && $method === $piece = $_POST['piece'] ?? '1'; $instrument = $_POST['instrument'] ?? ''; $version = $_POST['version'] ?? '1'; - $filename = $_POST['filename'] ?? ''; + $key = $_POST['key'] ?? ''; + $clef = $_POST['clef'] ?? ''; + $variant = $_POST['variant'] ?? ''; + $part = $_POST['part'] ?? '1'; - $result = $scanner->uploadPdf($scoreId, $file, $piece, $instrument, $version, $filename); + $result = $scanner->uploadPdf($scoreId, $file, $piece, $instrument, $version, $key, $clef, $variant, $part); if ($result['success']) { echo json_encode(['success' => true, 'path' => $result['path']]); diff --git a/api/lib/ScoreScanner.php b/api/lib/ScoreScanner.php index dd375cd..62bbca1 100644 --- a/api/lib/ScoreScanner.php +++ b/api/lib/ScoreScanner.php @@ -474,7 +474,7 @@ class ScoreScanner { rmdir($dir); } - public function uploadPdf(string $scoreId, array $file, string $piece, string $instrument, string $version, string $filename): array { + public function uploadPdf(string $scoreId, array $file, string $piece, string $instrument, string $version, string $key = '', string $clef = '', string $variant = '', string $part = '1'): array { $scoreDir = $this->scoresPath . $scoreId; if (!is_dir($scoreDir)) { @@ -484,18 +484,49 @@ class ScoreScanner { // Create directory structure: scoreId/piece/instrument/version $targetDir = $scoreDir . '/' . $piece . '/' . $instrument . '/' . $version; - if (!mkdir($targetDir, 0755, true)) { - return ['success' => false, 'error' => 'Failed to create directory']; + if (!is_dir($targetDir)) { + if (!mkdir($targetDir, 0755, true)) { + return ['success' => false, 'error' => 'Failed to create directory']; + } } - // Determine filename - if (empty($filename)) { - $filename = $file['name']; + // Map instrument code to name + $instrumentNames = [ + 'dir' => 'direction', 'pic' => 'piccolo', 'flu' => 'flute', 'cla' => 'clarinette', + 'clb' => 'clarinette_basse', 'sax' => 'saxophone_alto', 'sat' => 'saxophone_tenor', + 'sab' => 'saxophone_baryton', 'coa' => 'cor_anglais', 'htb' => 'hautbois', + 'bas' => 'basson', 'cor' => 'cor', 'trp' => 'trompette', 'crn' => 'cornet', + 'trb' => 'trombone', 'eup' => 'euphonium', 'tub' => 'tuba', 'cba' => 'contrebasse', + 'per' => 'percussion', 'pia' => 'piano', 'har' => 'harpe' + ]; + + $instName = $instrumentNames[$instrument] ?? $instrument; + + // Build filename: instrument_variant_key_clef_part.pdf + $filenameParts = [$instName]; + if (!empty($variant)) { + $filenameParts[] = $variant; } + if (!empty($key)) { + $filenameParts[] = $key; + } + if (!empty($clef)) { + $filenameParts[] = $clef; + } + $filenameParts[] = $part; + + $filename = implode('_', $filenameParts) . '.pdf'; $targetPath = $targetDir . '/' . $filename; - if (move_uploaded_file($file['tmp_name'], $targetPath)) { + // Use copy for CLI testing, move_uploaded_file for real uploads + if (is_uploaded_file($file['tmp_name'])) { + $result = move_uploaded_file($file['tmp_name'], $targetPath); + } else { + $result = copy($file['tmp_name'], $targetPath); + } + + if ($result) { return ['success' => true, 'path' => "$scoreId/$piece/$instrument/$version/$filename"]; } diff --git a/api/php-upload.ini b/api/php-upload.ini new file mode 100644 index 0000000..da05b43 --- /dev/null +++ b/api/php-upload.ini @@ -0,0 +1,2 @@ +upload_max_filesize = 64M +post_max_size = 64M diff --git a/partitions/src/lib/api.ts b/partitions/src/lib/api.ts index 0430d20..64bcc82 100644 --- a/partitions/src/lib/api.ts +++ b/partitions/src/lib/api.ts @@ -112,5 +112,39 @@ export const apiService = { token = state.token || ''; })(); return `${API_BASE_URL}/download/${path}?token=${token}`; + }, + + async createScore(name: string, compositor: string): Promise<{ success: boolean; score?: any; error?: string }> { + const response = await api.post('/admin/scores', { name, compositor }); + return response.data; + }, + + async updateScore(id: string, name: string, compositor: string): Promise<{ success: boolean; error?: string }> { + const response = await api.put(`/admin/scores/${id}`, { name, compositor }); + return response.data; + }, + + async deleteScore(id: string): Promise<{ success: boolean; error?: string }> { + const response = await api.delete(`/admin/scores/${id}`); + return response.data; + }, + + async uploadPdf(scoreId: string, file: File, piece: string, instrument: string, version: string, key?: string, clef?: string, variant?: string, part?: string): Promise<{ success: boolean; path?: string; error?: string }> { + const formData = new FormData(); + formData.append('file', file); + formData.append('piece', piece); + formData.append('instrument', instrument); + formData.append('version', version); + if (key) formData.append('key', key); + if (clef) formData.append('clef', clef); + if (variant) formData.append('variant', variant); + if (part) formData.append('part', part); + + const response = await api.post(`/admin/scores/${scoreId}/upload`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + return response.data; } }; diff --git a/partitions/src/routes/+layout.svelte b/partitions/src/routes/+layout.svelte index d79842f..ff983c9 100644 --- a/partitions/src/routes/+layout.svelte +++ b/partitions/src/routes/+layout.svelte @@ -9,9 +9,11 @@ let isAuthenticated = false; let currentPath = '/'; + let userRole = ''; auth.subscribe((state) => { isAuthenticated = !!state.token; + userRole = state.user?.role || ''; }); page.subscribe(($page) => { @@ -48,6 +50,11 @@ + + + {#if error} +
+ {error} +
+ {/if} + + +
+ + + {#if showForm} +
+
{ e.preventDefault(); createScore(); }} class="space-y-4"> +
+ + +
+
+ + +
+ +
+
+ {/if} +
+ + +
+

Uploader un PDF

+
{ e.preventDefault(); uploadPdf(); }} class="space-y-4"> +
+
+ + +
+
+ + +
+
+ +
+
+ + {#if showAdvanced} +
+ {#if selectedScorePieceCount > 1} +
+ + +
+ {/if} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {/if} + +
+

+ Nom attendu: {expectedFilename || 'Sélectionnez un instrument'} +

+

+ Le fichier sera renommé automatiquement lors de l'upload +

+
+ +
+ + +
+ {#if uploadError} +

{uploadError}

+ {/if} + {#if uploadSuccess} +

{uploadSuccess}

+ {/if} + +
+
+ + +
+ + + + + + + + + + + {#each scores as score} + + + + + + + {/each} + +
IDNomCompositeurActions
{score.id}{score.name}{score.compositor || '-'} +
+ + Voir + + +
+
+
+ {/if} +