diff --git a/TODO.md b/TODO.md index 14b0bcf..d129b4e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ # TODO -> Last updated: 2026-06-11 11:42 +> Last updated: 2026-06-11 12:10 > Context: Form Accessibility & Resilience improvements for XAMXAM thesis submission platform ## In Progress @@ -9,12 +9,12 @@ - [ ] #aria-test-manual Test WCAG changes with VoiceOver and NVDA on full add/edit/partage form flows - [ ] #nojs-upload-test Test end-to-end: submit partage form with JS disabled, verify files arrive via `$_FILES` -- [ ] #two-phase-commit Add two-phase commit: INSERT thesis `status='draft'`, COMMIT, move files, UPDATE to `active` `(ThesisCreateController.php)` - [ ] #cleanup-drafts Add periodic cleanup job for orphaned drafts (`just cleanup-drafts`) - [ ] #form-setup-helper Add `ThesisFormSetup` helper class to reduce bootstrap duplication across add/edit/partage `(partage/index.php)` `(admin/add.php)` `(admin/edit.php)` ## Completed +- [x] #two-phase-commit Add two-phase commit: INSERT thesis `status='draft'`, COMMIT, move files, UPDATE to `active` `(ThesisCreateController.php)` ✓ - [x] #filepond-preserve Preserve FilePond temp file IDs on partage validation redirect `(partage/index.php)` `(FilepondHandler.php)` ✓ - [x] #refactor-partage Extract partage form page chrome to `templates/partage/form-page.php` `(partage/index.php)` ✓ - [x] #htmx-migration HTMX v2 migration: OverType editors, autosave handler, backend `HX-Request` detection ✓ diff --git a/app/migrations/applied/039_add_thesis_status.sql b/app/migrations/applied/039_add_thesis_status.sql new file mode 100644 index 0000000..b26c57a --- /dev/null +++ b/app/migrations/applied/039_add_thesis_status.sql @@ -0,0 +1,14 @@ +-- 039: Add a status column to theses to track submission lifecycle +-- +-- Lifecycle: +-- draft — thesis row created, file operations not yet completed (or failed) +-- active — all file operations succeeded, submission is complete +-- +-- Separate from is_published (visibility). The admin can filter drafts +-- to find orphaned/broken submissions and the cleanup-drafts job targets +-- status='draft' rows older than a threshold. + +ALTER TABLE theses ADD COLUMN status TEXT NOT NULL DEFAULT 'active'; + +-- Existing theses already have files → they are active. +-- New theses start as draft and are promoted to active after file ops succeed. diff --git a/app/src/Controllers/ThesisCreateController.php b/app/src/Controllers/ThesisCreateController.php index 56bf803..2f2ddbc 100644 --- a/app/src/Controllers/ThesisCreateController.php +++ b/app/src/Controllers/ThesisCreateController.php @@ -93,13 +93,14 @@ class ThesisCreateController * recapitulatif.php?id=. On validation or DB failure, throws an Exception * (caller must flash the message and redirect back to the form). * - * Execution order: + * Two-phase execution: * 1. Validate + sanitise POST fields * 2. Find/create author record - * 3. INSERT thesis row + link author (inside transaction) + * 3. INSERT thesis row (status='draft') + link author (inside transaction) * 4. Link jury, languages, formats, tags (inside transaction) - * 5. COMMIT + * 5. COMMIT (thesis visible only as draft) * 6. Handle file uploads: cover, thesis files (outside transaction) + * 7. UPDATE status to 'active' (confirms file operations succeeded) * * @param array $post Sanitised $_POST array. * @param array $files $_FILES array. @@ -226,6 +227,14 @@ class ThesisCreateController // ── 6. Website URL — stored as thesis_files row ────────────────────── $this->handleWebsiteUrl($thesisId, $post); + // ── 7. Two-phase commit: mark submission complete ────────────────── + // The thesis was committed as status='draft' before file operations. + // Now that all files are safely in place, promote to 'active'. + // If any file operation had thrown, the draft would remain orphaned + // for the periodic cleanup job (just cleanup-drafts). + $this->db->setThesisStatus($thesisId, 'active'); + error_log("[ThesisCreate] ACTIVE — thesis_id=$thesisId | all file operations succeeded"); + return $thesisId; } diff --git a/app/src/Database.php b/app/src/Database.php index 5c0c8ae..4b3460c 100644 --- a/app/src/Database.php +++ b/app/src/Database.php @@ -1682,6 +1682,22 @@ class Database Audit::log($this, Audit::actor(), 'UPDATE', 'theses', $thesisId, $old, $new); } + /** + * Set the submission lifecycle status of a single thesis. + * + * Valid statuses: 'draft' (files not yet moved / failed) → 'active' (complete). + */ + public function setThesisStatus(int $thesisId, string $status): void + { + require_once __DIR__ . '/Audit.php'; + $old = $this->fetchRow('theses', $thesisId); + $this->pdo->prepare( + 'UPDATE theses SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?' + )->execute([$status, $thesisId]); + $new = $this->fetchRow('theses', $thesisId); + Audit::log($this, Audit::actor(), 'UPDATE', 'theses', $thesisId, $old, $new); + } + /** * Set the published state for multiple theses at once. * @param int[] $thesisIds @@ -2242,12 +2258,12 @@ class Database baiu_link, license_id, license_custom, access_type_id, objet, - is_published, + is_published, status, remarks, jury_points, exemplaire_baiu, exemplaire_erg, cc2r, submitted_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, "draft", ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) '); $validObjet = ['tfe', 'thèse', 'frart']; diff --git a/app/storage/schema.sql b/app/storage/schema.sql index 46c5ad0..467b9cb 100644 --- a/app/storage/schema.sql +++ b/app/storage/schema.sql @@ -97,6 +97,7 @@ CREATE TABLE IF NOT EXISTS theses ( defense_date DATETIME, published_at DATETIME, is_published BOOLEAN DEFAULT 0, + status TEXT NOT NULL DEFAULT 'active', baiu_link TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,