Files
xamxam/TODO.md
Pontoporeia e4be230a04 admin/system: add nginx config viewer tab
Add a 'nginx — config' tab to the Système admin page (system.php).

- Reads /etc/nginx/sites-available/posterg (live deployed config) first;
  falls back to nginx/posterg.conf (local reference copy) when the live
  path is inaccessible (e.g. in dev, or wrong permissions).
- Displays a colour-coded badge: green '● Config déployée' for live,
  amber '⚠ Référence locale' for the fallback.
- Renders the full config in the shared .log-output code block with
  line numbers (data-n gutter via CSS ::before) and lightweight nginx
  syntax colouring (comments grey, block keywords purple, directives blue).
- Reuses the existing copy-to-clipboard button.
- Tab routing: activeTab validation extended to accept 'nginx_config';
  log pre-loading guards skip when activeTab is 'nginx_config'.
- No remote execution: read-only, zero new attack surface.
2026-03-26 11:23:18 +01:00

15 KiB
Raw Blame History

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

NEW FEATURES

1 — License page (public)

Create a public-facing /licence.php page, styled consistently with apropos.php.

  • public/licence.php — new public page; fetches content from pages table (slug 'licenses'); renders with Parsedown Markdown; uses apropos.css layout
  • templates/nav.php — add "Licence" link between "Répertoire" and "À Propos"; apply site-nav__link--active when $currentNav === 'licence'
  • The pages table row for slug 'licenses' verified in live DB

2 — Admin: WYSIWYG/Markdown editors for static pages

Allow admins to edit the content of the "À propos" and "Licence" pages from the admin panel, stored in the existing pages table.

2a — src/Database.php

  • getPage(string $slug): array|nullSELECT * FROM pages WHERE slug = ?
  • savePage(string $slug, string $content): void — throws if slug not found
  • getAllPages(): array — for listing in admin

2b — Admin pages editor UI

  • public/admin/pages.php — list all editable pages; links to edit each one
  • public/admin/pages-edit.php — EasyMDE WYSIWYG Markdown editor via CDN

2c — public/admin/actions/page.php

  • Auth guard + CSRF check + slug validation + length validation + savePage + redirect

2d — Public pages render Markdown

  • public/apropos.php — renders $db->getPage('about') via Parsedown (bundled src/Parsedown.php)
  • public/licence.php — renders $db->getPage('licenses') via Parsedown
  • Parsedown bundled as src/Parsedown.php (zero-dependency, MIT)
  • templates/admin/head.php — "Pages statiques" nav item added

3 — License field on TFE forms

Add a "Licence" dropdown to the Add and Edit TFE forms. The license_types table already exists in the schema with an id, name, description structure but has no seed data yet.

3a — Schema / DB

  • storage/schema.sql — seed INSERT OR IGNORE for 8 CC licence types added
  • storage/migrations/003_seed_license_types.sql — migration created + applied
  • Verified live DB has license_types with 8 rows

3b — src/Database.php

  • getLicenseTypes(): array
  • getAllLicenseTypes(): array — alias

3c — Add form (public/admin/add.php)

  • Loads $licenseTypes; "Licence" <select name="license_id"> added before duration

3d — Add action (public/admin/actions/formulaire.php)

  • $licenseId parsed + included in INSERT

3e — Edit form (public/admin/edit.php)

  • Loads $licenseTypes; raw license_id FK fetched directly; select pre-populated
  • POST handler: license_id included in UPDATE

3f — View update

  • storage/schema.sqlv_theses_full now exposes t.license_id raw FK (done as part of 004/005 view rebuild)

3g — TFE public page

  • public/tfe.php — "Licence :" meta row added, shown when non-null

4 — Jury composition section in Add/Edit forms

Replace the current flat "promoteur interne / externe" fields with a structured Composition du jury section: président·e, promoteur·ice, lecteur·ices (mixed internal/external).

4a — Schema / DB

Current state: supervisors table + thesis_supervisors junction with a bare supervisor_order integer. No role distinction.

  • storage/migrations/004_jury_roles.sql: - ALTER TABLE thesis_supervisors ADD COLUMN role TEXT NOT NULL DEFAULT 'promoteur' — role values: 'president', 'promoteur', 'lecteur' - ALTER TABLE thesis_supervisors ADD COLUMN is_external INTEGER NOT NULL DEFAULT 0 — 1 = external, 0 = internal (replaces the old "promoteur externe" free-text field) - No data loss: existing rows get role = 'promoteur', is_external = 0
  • storage/schema.sql — add the two new columns to thesis_supervisors definition; update v_theses_full to expose jury members grouped by role: - GROUP_CONCAT(DISTINCT CASE WHEN ts.role='president' THEN s.name END) as jury_president - GROUP_CONCAT(DISTINCT CASE WHEN ts.role='promoteur' THEN s.name END) as jury_promoteurs - GROUP_CONCAT(DISTINCT CASE WHEN ts.role='lecteur' THEN s.name END) as jury_lecteurs - Keep the existing supervisors column (all names) for backwards compat - Migration SQL must DROP + CREATE the view

4b — src/Database.php

  • getThesisJury(int $thesisId): array — fetch all supervisors for a thesis with their role and is_external flag
  • setThesisJury(int $thesisId, array $juryMembers): void — delete + re-insert

4c — Add form (public/admin/add.php)

  • Remove promoteurice and promoteurice_externe fields
  • Add "Composition du jury" fieldset: président·e, promoteur·ice + externe checkbox, dynamic lecteur·ices list with JS add/remove

4d — Add action (public/admin/actions/formulaire.php)

  • Removed old promoteurice parsing; parse jury fields; call $db->setThesisJury()

4e — Edit form (public/admin/edit.php)

  • Load $jury = $db->getThesisJury($thesisId); jury fieldset pre-populated from DB
  • POST handler calls $db->setThesisJury()

4f — TFE public page (public/tfe.php)

  • Three conditional jury rows: Président·e, Promoteur·ice, Lecteur·ices

5 — Banner image upload for home page cards

Each TFE can have an optional banner image used as its home page card thumbnail. This is distinct from the existing "couverture" file concept (which goes into thesis_files as type 'cover') — the banner is specifically optimised for the home grid (wider, shorter aspect ratio).

5a — Schema / DB

  • storage/migrations/005_add_banner.sqlALTER TABLE theses ADD COLUMN banner_path TEXT
  • storage/schema.sqlbanner_path TEXT in theses; t.banner_path in view

5b — Add form (public/admin/add.php)

  • "Image bannière" file input added after couverture row

5c — Add action (public/admin/actions/formulaire.php)

  • Banner upload: MIME check, 5 MB cap, save to banners/, call setBannerPath()

5d — Edit form (public/admin/edit.php)

  • Banner preview img shown; remove_banner checkbox; new banner upload input
  • POST handler: unlinks old file on remove; processes new upload via setBannerPath()

5e — src/Database.php

  • banner_path exposed via view — verified; getThesisById() / getPublishedTheses() pick it up automatically
  • setBannerPath(int $thesisId, ?string $path): void added

6 — Home page: gradient placeholder cards & random ordering

6a — Gradient placeholder for cards without a banner

  • public/index.php — gradient placeholder using HSL hue from thesis ID
  • public/assets/main.css.card__media--gradient, .card__gradient-author, .card__gradient-title styles added

6b — Banner image as card thumbnail

  • public/index.php — checks banner_path first, falls through to gradient

6c — Random ordering from the latest year

  • src/Database.phpgetLatestYearTheses(int $limit = 24) + getLatestPublishedYear()
  • public/index.php — default home view uses random latest-year selection; paginated view for ?year=X and ?page=N; info label shown

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; column keywordname
  • Rename junction thesis_keywordsthesis_tags; FK keyword_idtag_id
  • PK on thesis_tags(tag_id, thesis_id); idx_tags_name; updated index names
  • Views v_theses_full/v_theses_public use thesis_tags/tags.name
  • Migration storage/migrations/001_rename_keywords_to_tags.sql written and applied

2 — src/Database.php

  • findOrCreateTag() added; findOrCreateKeyword() is a backwards-compat alias
  • getUsedTags() rewritten with proper M2M JOIN; getUsedKeywords() alias kept
  • buildSearchConditions: keyword/query use EXISTS subquery on thesis_tags/tags
  • All conditions prefixed with vp. to match view alias; vp alias added to search queries

3 — Admin write paths

  • public/admin/actions/formulaire.php: uses findOrCreateTag + thesis_tags
  • public/admin/edit.php: DELETE FROM thesis_tags + findOrCreateTag + thesis_tags

4 — Public read paths

  • public/search.php: fixed $kw['keyword']$kw['name'] (tag column rename)
  • getUsedKeywords() alias delegates to getUsedTags() — no functional change needed
  • public/tfe.php: $data['keywords'] still works — view column name unchanged
  • templates/search-bar.php: no keyword param refs — verified

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

5a — src/Database.php

  • getAllTagsWithCount(), renameTag(), mergeTag(), deleteTag()

5b — public/admin/tags.php

  • Auth guard, CSRF, table with rename/merge/delete per row, inline forms

5c — public/admin/actions/tag.php

  • Routes on $_POST['action']: rename, merge, delete

5d — Nav & routing

  • templates/admin/head.php: "Mots-clés" nav link added

5e — Propagation safety

  • mergeTag() uses INSERT OR IGNORE to avoid PK conflicts; deleteTag() cascades via FK

6 — Tests

  • tests/Unit/DatabaseTest.php: tests 57 cover findOrCreateTag, getUsedTags, alias
  • tests/Integration/SearchTest.php: tests 46 cover tag-filter subquery, full-text query, count consistency

6 — Fixtures / seed data

  • storage/fixtures/CreateTestDatabase.php: updated to tags/thesis_tags/findOrCreateTag()

Feature: Mode Maintenance

  • Storage flag file storage/maintenance.flag (created on demand)
  • Public gate in config/bootstrap.php — blocks non-admin routes when flag exists
  • public/maintenance.php (503 page, minimal dark UI)
  • public/admin/actions/maintenance.php (POST: enable/disable)
  • Admin UI toggle in public/admin/index.php (bar with status + action button)

Feature: TFE Visibility States (publique / interne / interdit)

  • DB migration 002_add_visibility.sql — seeds access_types rows (already existed)
  • src/Database.phpsetVisibility(), bulkSetVisibility(), getAccessTypes()
  • public/media.php — blocks thesis files when access_type_id = 3 (Interdit)
  • public/tfe.php — shows access type, context_note, hides files for Interdit
  • public/admin/edit.php — access_type_id select + context_note textarea
  • public/admin/index.php — three-state access badge per row
  • public/admin/actions/visibility.php — single + bulk visibility update

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
  • Remove 100-item cap from répertoire student index: getAllPublishedTheses() fetches all published theses; search results remain paginated at 30/page
  • Cover image fallback for home grid cards: batch-load thesis_files covers for theses without banner_path; resolution order: banner → cover → gradient

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 viewer to admin panel: "nginx — config" tab in system.php reads /etc/nginx/sites-available/posterg (live, with badge) or falls back to local nginx/posterg.conf; line-numbered, syntax-coloured, copy-to-clipboard
  • Add admin user management UI — password change/set for PHP auth layer (public/admin/account.php + actions/account.php; "Compte" nav link; account CSS)
  • Merge status.php and logs.php into a single system.php page; remove "Statut" and "Journaux" nav links, add single "Système" link; preserve all existing content in their respective sections
  • Rework logs UI: replace the select-then-click-Afficher flow with instant tabs (nginx access, nginx error, php-fpm); switching tabs loads the selected log immediately without a form submit; add a copy-to-clipboard button per log view