mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
docs: add admin tag management UI task to TODO
This commit is contained in:
94
TODO.md
94
TODO.md
@@ -100,7 +100,99 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis
|
|||||||
- [ ] `public/tfe.php`: `$data['keywords']` → `$data['tags']` (view column rename)
|
- [ ] `public/tfe.php`: `$data['keywords']` → `$data['tags']` (view column rename)
|
||||||
- [ ] `templates/search-bar.php` (if applicable): update any hardcoded `keyword` param refs
|
- [ ] `templates/search-bar.php` (if applicable): update any hardcoded `keyword` param refs
|
||||||
|
|
||||||
### 5 — Tests
|
### 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/Unit/DatabaseTest.php`: add test for `findOrCreateTag` round-trip
|
||||||
- [ ] `tests/Integration/SearchTest.php`: add test for tag-filter search using the new
|
- [ ] `tests/Integration/SearchTest.php`: add test for tag-filter search using the new
|
||||||
|
|||||||
Reference in New Issue
Block a user