Files
xamxam/TODO.md
2026-03-23 11:03:19 +01:00

12 KiB

TODO

Styling Redesign (matching design images)

  • Redesign shared nav bar (purple gradient top, flat, POSTERG / RÉPERTOIRE / À PROPOS)
  • Redesign shared search bar (full-width, icon, bottom border only, white bg)
  • Rewrite common.css (nav + search bar components)
  • Rewrite main.css (home page — white bg, media card grid, label below)
  • Rewrite search.css (répertoire index — 4-col ANNÉES/CATÉGORIES/ÉTUDIANTES/MOTS-CLÉS)
  • Rewrite tfe.css (TFE page — 2-col, large author/title left, media right)
  • Add apropos.css (À Propos — 2-col, large monospace text)
  • Rewrite admin.css (dark bg, purple gradient nav, bottom-border-only form inputs)
  • Update templates/nav.php (new shared nav partial)
  • Update templates/search-bar.php (new shared search bar partial)
  • Rewrite public/index.php (home page with new layout)
  • Rewrite public/search.php (répertoire index view + search results view)
  • Rewrite public/tfe.php (individual TFE page)
  • Create public/apropos.php (À Propos page)
  • Rewrite templates/admin/head.php (admin nav)
  • Rewrite templates/admin/footer.php (clean close)
  • Rewrite public/admin/add.php (form with row layout)
  • Rewrite public/admin/index.php (dark table)
  • Rewrite public/admin/edit.php (form with row layout)
  • Rewrite public/admin/login.php (centered dark login box)
  • Rewrite public/admin/thanks.php (dark info cards)
  • Rewrite public/admin/import.php (clean dark form)

Justfile / Ops

  • Simplify serve and deploy to one recipe each
  • Remove sysadmin recipes (server-logs, server-status, deploy-nginx, deploy-admin-tools)
  • Extract server scripts to scripts/ (deploy-server.sh, manage-admin-users.sh)
  • Guard deploy-db against overwriting existing remote database
  • Update README.md and docs/SERVER_SETUP.md to reflect current structure

Refactor: M2M tags via tags + thesis_tags junction table

The current schema stores keywords in a keywords table joined via thesis_keywords. The field column is named keyword (not name), breaking the naming convention used by every other lookup table (orientations.name, format_types.name, etc.). More critically, buildSearchConditions and the view v_theses_full filter keywords through GROUP_CONCAT strings with LIKE, bypassing the junction table entirely.

Goal: rename the tables and column to the canonical M2M pattern (tags, thesis_tags, tags.name), add the missing index, and rewrite all tag queries to use a proper JOIN.

1 — Schema migration (storage/schema.sql + live DB)

  • Rename table keywordstags; rename column keywords.keywordtags.name
  • Rename junction table thesis_keywordsthesis_tags; rename FK column thesis_keywords.keyword_idthesis_tags.tag_id
  • Composite PK on thesis_tags(tag_id, thesis_id) (tag first — matches the lookup pattern WHERE t.name = ?)
  • Add index idx_tags_name ON tags(name) (supports exact-match lookup on insert/find)
  • Update idx_thesis_keywords_* index names → idx_thesis_tags_thesis, idx_thesis_tags_tag
  • Update view v_theses_full / v_theses_public: replace LEFT JOIN keywords k ON tk.keyword_id = k.id … GROUP_CONCAT(DISTINCT k.keyword) with LEFT JOIN tags t ON tt.tag_id = t.id … GROUP_CONCAT(DISTINCT t.name)
  • Write and test a SQLite migration script (storage/migrations/001_rename_keywords_to_tags.sql)

2 — src/Database.php

  • findOrCreateKeyword()findOrCreateTag(): query tags table, column name
  • getUsedKeywords()getUsedTags(): rewrite to use proper M2M JOIN instead of querying the view: sql SELECT DISTINCT t.* FROM tags t JOIN thesis_tags tt ON t.id = tt.tag_id JOIN theses th ON tt.thesis_id = th.id WHERE th.is_published = 1 ORDER BY t.name
  • buildSearchConditions: replace the keywords LIKE :keyword view-string hack with a subquery using the junction table: sql EXISTS ( SELECT 1 FROM thesis_tags tt JOIN tags t ON t.id = tt.tag_id WHERE tt.thesis_id = theses.id AND t.name LIKE :keyword ESCAPE '\' ) (search still runs on v_theses_public; the subquery references the base table)
  • validateSearchParams: rename key 'keyword''tag' (or keep alias for backwards-compat during transition)
  • Add backwards-compat alias findOrCreateKeywordfindOrCreateTag and getUsedKeywordsgetUsedTags (remove after all callers updated)

3 — Admin write paths

  • public/admin/actions/formulaire.php: replace findOrCreateKeyword + INSERT INTO thesis_keywords with findOrCreateTag + INSERT INTO thesis_tags
  • public/admin/edit.php: same replacement in keyword update block (DELETE FROM thesis_keywordsDELETE FROM thesis_tags, insert loop)

4 — Public read paths

  • public/search.php: rename $keywords$tags; update getUsedKeywords() call → getUsedTags(); rename GET param keywordtag (keep old param as alias)
  • public/tfe.php: $data['keywords']$data['tags'] (view column rename)
  • templates/search-bar.php (if applicable): update any hardcoded keyword param refs

5 — Admin tag management UI (/admin/tags.php)

The goal is a dedicated page for viewing, renaming, merging, and deleting tags, with full referential-integrity awareness (no orphan thesis_tags rows, no broken search results).

5a — src/Database.php — new tag-management methods

  • getAllTagsWithCount(): array — return all tags with a thesis_count column: sql SELECT t.id, t.name, COUNT(tt.thesis_id) AS thesis_count FROM tags t LEFT JOIN thesis_tags tt ON t.id = tt.tag_id GROUP BY t.id ORDER BY t.name
  • renameTag(int $id, string $newName): void — UPDATE tags SET name = ? WHERE id = ?; unique-constraint violation must be caught and re-thrown as a user-readable InvalidArgumentException
  • mergeTag(int $sourceId, int $targetId): void — within a transaction: 1. INSERT OR IGNORE into thesis_tags(tag_id, thesis_id) for every row in thesis_tags WHERE tag_id = $sourceId (re-point to target, skip dupes) 2. DELETE FROM thesis_tags WHERE tag_id = $sourceId 3. DELETE FROM tags WHERE id = $sourceId
  • deleteTag(int $id): void — within a transaction: DELETE FROM thesis_tags WHERE tag_id = ? first (FK cascade may handle this after the schema migration, but an explicit delete is safer), then DELETE FROM tags WHERE id = ?; reject with exception if thesis_count > 0 and the caller did not pass $force = true

5b — public/admin/tags.php — list + inline-edit view

  • Auth guard: AdminAuth::requireLogin() at top; CSRF token in session
  • Page title: "Gestion des mots-clés" (reuses $pageTitle for head.php)
  • Load getAllTagsWithCount() for display
  • Table columns: Mot-clé (editable inline) · Nb de TFE · Actions
  • Each row has:
    • An inline <form> (POST to actions/tag.php, action=rename) pre-filled with the current tag name, so the admin can edit in-place and submit per row
    • A Fusionner → button that reveals a <select> of other tags and a confirm submit (POST action=merge, fields: source_id, target_id)
    • A Supprimer button — disabled / greyed if thesis_count > 0; otherwise submits POST action=delete, field: tag_id; requires JS confirm() before submit
  • Flash success/error messages from $_SESSION['success'] / $_SESSION['error'] (same pattern as index.php)
  • Empty state: "Aucun mot-clé enregistré." if table is empty
  • Stats bar (reuse .admin-stats CSS): total tag count, total TFE with ≥1 tag, total tags with 0 TFE (orphan count)

5c — public/admin/actions/tag.php — POST action handler

  • Auth guard + CSRF check (same pattern as publish.php)
  • Route on $_POST['action']:
    • 'rename': validate tag_id (int > 0) and name (non-empty, max 100 chars, trimmed); call renameTag(); set $_SESSION['success']
    • 'merge': validate source_id and target_id are distinct positive ints; call mergeTag(); set success message including both tag names
    • 'delete': validate tag_id; call deleteTag(); set success message
  • On any exception: set $_SESSION['error']
  • Regenerate CSRF token after every action
  • Redirect back to /admin/tags.php in all cases

5d — Nav & routing

  • templates/admin/head.php: add nav link <a href="/admin/tags.php" …>Mots-clés</a> between "Ajouter un TFE" and "Importer une liste de TFE"; apply active class on tags.php
  • nginx/posterg.conf (or equivalent): if using try_files / location blocks, confirm /admin/tags.php and /admin/actions/tag.php are reachable (likely already covered by the existing *.php rule, but verify)

5e — Propagation safety checklist

These must remain unbroken after the tag-management UI is live:

  • Renaming a tag updates tags.name; v_theses_full reads from tags directly, so GROUP_CONCAT output updates automatically — no view rebuild needed
  • Renaming a tag does not affect thesis_tags rows (only the name column changes); search results remain intact
  • Merging tag A→B: all theses that had tag A now appear under tag B; the EXISTS subquery in buildSearchConditions finds them via thesis_tags
  • Deleting a tag with thesis_count > 0 is blocked by default (explicit $force parameter required); the UI never shows a delete button for in-use tags — double protection
  • edit.php keyword field renders the current CSV of tag names from $thesis['keywords'] (view column); after a rename the edit page will show the new name on next load — no extra work needed
  • The public search.php keyword filter ($_GET['tag']) uses tag names; after a rename, any bookmarked URL with the old name will return 0 results (expected behaviour — no redirect needed, but note it in a code comment)

6 — Tests

  • tests/Unit/DatabaseTest.php: add test for findOrCreateTag round-trip
  • tests/Integration/SearchTest.php: add test for tag-filter search using the new subquery path (not the LIKE-on-GROUP_CONCAT path)

6 — Fixtures / seed data

  • storage/fixtures/CreateTestDatabase.php: update to use tags / thesis_tags table names and findOrCreateTag()

Pending

  • Add flake.nix for Nix-based PHP dev environment
  • Add favicon (<link rel="icon"> → admin_favicon.svg) to all pages; nginx 204 for /favicon.ico
  • Add pagination to répertoire student index (currently capped at 100)
  • Thumbnail generation / cover image support for home grid cards

Admin / Server

  • Create scripts/setup-server.sh (one-time server setup: group, ownership, setgid 2775 on dirs)
  • Add just setup-server recipe (rsync + run setup-server.sh on remote)
  • Exclude .claude and .pi from rsync deploy
  • Update docs/SERVER_SETUP.md with correct permissions rationale and troubleshooting
  • Add server status view in admin panel (nginx + php-fpm health, site HTTP check)
  • Add server log viewer in admin panel (tail nginx error/access logs via SSH or log endpoint)
  • Add nginx config deploy flow to admin panel (upload scripts/deploy-server.sh, run remotely)
  • Add admin user management UI (wraps scripts/manage-admin-users.sh on server)