feat: prevent duplicate TFE submissions with logging and user feedback

- Add DuplicateThesisException (typed, carries existing thesis metadata)
- Add Database::findDuplicateThesis(): matches on year + author + normalised
  title (exact, prefix, Levenshtein ≤10% of longer string)
- ThesisCreateController::submit() runs duplicate check before any DB write
  and throws DuplicateThesisException on match
- AppLogger::logDuplicate() writes status=duplicate entries to the JSON-lines
  log for audit purposes
- App::flash/consumeFlash extended to support 'warning' flash type
- admin/actions/formulaire.php: catches DuplicateThesisException, logs it,
  flashes an HTML warning toast with a clickable link to the existing thesis,
  and repopulates the form fields
- partage/index.php: same catch block; surfaces a plain-text flash-warning
  banner on the student form with identifier, title, and year of the match;
  form is repopulated via session
- toast.php: renders toast--warning variant
- admin.css: .toast--warning + link colour rules
- form.css: .flash-warning style for the partage form
This commit is contained in:
Pontoporeia
2026-05-04 16:29:31 +02:00
parent 0a05f3911c
commit a2cba6d3c0
35 changed files with 1726 additions and 1302 deletions

35
TODO.md
View File

@@ -1,24 +1,13 @@
# TODO
# XAMXAM TODO
- [x] Refactor `justfile` to reduce redundancy and merge similar recipes
- [x] Merge `deploy-*` recipes into a single `deploy-script` recipe
- [x] Remove rarely used recipes (`show id`, `setup-dirs`)
- [x] Simplify `test-*` recipes
- [x] Remove redundant `default` recipe
- [x] Preserve all critical functionality
- [x] Enhance `serve` recipe to automatically open the browser
- [x] Keep `serve` recipe in the foreground (browser open backgrounded, PHP server blocks)
- [x] Add `psalm` recipe (auto-inits config on first run, then analyses)
- [x] Fix all genuine Psalm errors (InvalidOperand, UnusedVariable, InvalidReturnType, NullableReturnStatement, InvalidArrayOffset, UnusedForeachValue, RedundantFunctionCall)
- [x] Generate psalm-baseline.xml to suppress false positives (UndefinedConstant, PossiblyUnused*, UnusedClass)
- [x] Add `lint-biome` recipe; fix all JS errors and warnings (arrow functions, template literals, noRedundantUseStrict, noUnusedVariables, useIterableCallbackReturn)
- [x] Replace Psalm with PHPStan + PHP-CS-Fixer
- [x] Remove vimeo/psalm and all its deps from vendor/
- [x] Install phpstan.phar (2.1.54) and php-cs-fixer.phar (3.95.1) in vendor/bin/
- [x] Create phpstan.neon (level 5, bootstraps app/bootstrap.php, scanFiles Parsedown)
- [x] Generate phpstan-baseline.neon (10 pre-existing errors baselined)
- [x] Create .php-cs-fixer.dist.php (PSR-12 + PHP80Migration, targets app/src + app/tests)
- [x] Replace `psalm` justfile recipe with `phpstan`, `cs-check`, `cs-fix`
- [x] Remove psalm.xml, psalm-baseline.xml
- [x] Remove @psalm-suppress annotations from SmtpRelay.php and RateLimit.php
- [x] Add .phpstan.result.cache and .php-cs-fixer.cache to .gitignore
## Duplicate TFE submission prevention
- [x] `DuplicateThesisException` — typed exception carrying existing thesis metadata
- [x] `Database::findDuplicateThesis()` — year + author + normalised-title matching (exact, prefix, Levenshtein ≤10%)
- [x] `ThesisCreateController::submit()` — calls duplicate check before any DB write, throws `DuplicateThesisException`
- [x] `AppLogger::logDuplicate()` — dedicated log action (`status: duplicate`) for audit trail
- [x] `App::flash/consumeFlash` — extended to support `warning` type alongside `error`/`success`
- [x] `admin/actions/formulaire.php` — catches `DuplicateThesisException` separately; logs it; flashes HTML warning with link to existing thesis; repopulates form
- [x] `partage/index.php` — same catch block; plain-text warning (no admin link) surfaced on the student form via `flash-warning` banner; form repopulated
- [x] `toast.php` — renders `toast--warning` block
- [x] `admin.css``.toast--warning` style + link colour
- [x] `form.css``.flash-warning` style (partage form)