From 8300f2c4213b23caf3a81f40f180877ad1b181ae Mon Sep 17 00:00:00 2001 From: andre Date: Fri, 25 Sep 2026 17:44:27 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20CLAUDE.md=20=E2=80=94=20decis=C3=A3o=20?= =?UTF-8?q?de=20arquitetura=20escrita=20(fecha=206.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documenta o padrão atual (páginas = controller+vista, config/functions_*.php como models, sem front controller próprio) como decisão mantida, não herança silenciosa do CodeIgniter — mais auth partilhado, trash, CSRF, audit, multi-ginásio, platform-tools, deploy e Composer. Já bloqueado por HTTP (.htaccess já negava CLAUDE.md por nome). Inclui uma secção "What's not done" honesta: sem front controller real, sem DataTables, Bootstrap 4+5 em simultâneo — para não passar a imagem de que isto já está resolvido. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..557f185 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,119 @@ +# fitme3070 — architecture + +Backoffice + public site for a gym (ginásio) chain, plain PHP 8.4, no framework. This +file exists because the pattern below was inherited from a CodeIgniter 4 port and had +never been written down as a decision (audit 2026-09-24, item 6.1) — it describes what +the codebase actually does, kept deliberately, not a design proposal for a rewrite. + +## Request lifecycle + +**Every page is its own controller-and-view combined.** A page in `views/admin/*.php` +or `site/*.php` does, in order: bootstrap (`config/db.php` + `config/functions.php`), +an auth guard, read `$_POST`/`$_GET` and call model functions, then fall through into +its own HTML. There is no separate template layer and no front controller — 71+ PHP +files are directly reachable by URL (docroot is the repo root; `.htaccess` denies +partials, `shared/platform-tools/*.php`, and `*.md`, but doesn't allowlist). + +**This is a known, accepted gap, not an oversight** — see "What's not done" below. +Introducing a real front controller/router for the app's own pages would touch most of +`views/`, `.htaccess`'s friendly-URL block, and every hardcoded `base_url('views/...')` +call; it's a genuine rewrite, tracked separately, not bundled into this doc. + +## Data layer: `config/functions_*.php` as models + +SQL lives in `config/functions_*.php`, one file per domain (`functions_team.php`, +`functions_galerias.php`, `functions_gyms.php`, `functions_memberships.php`, …) plus +`functions_db.php` for cross-cutting `users` queries. Functions are named +`db__()` (`db_gyms_create`, `db_team_update`, `db_gallery_list`). **Views +never write SQL directly** — if you're adding a query, it goes in the matching +`functions_*.php`, not inline in the page. (Audit 6.3, closed 2026-09-25 — `grep -rl +'\$pdo->\(query\|prepare\|exec\)' views/` should always return nothing.) + +`config/functions.php` is the loader: every page requires `config/db.php` (opens +`$pdo`, starts the session, connects to platform-tools) then +`config/functions.php` (requires every `functions_*.php` in order). Read that file's +own header comment before adding a new one — order matters for a few (`auth.php` +before anything that calls `require_login()`, etc.). + +## Auth + +Login/session/roles run on `shared/platform-tools/functions_auth.php` (`'auth' => +'shared'` in `config/db.php`, migrated 2026-09-25 — audit 1.3). `config/auth.php` keeps +the app's own guard names (`require_login()`, `require_admin()`, `require_staff()`, +`require_sysadmin()`, `is_admin()`, etc.) as a thin layer over the shared primitives, so +none of the ~30 call sites in `views/` had to change. Role ladder: +`sysadmin`(100) > `admin`(80) > `staff`(40) > `client`(10, portal-only). "Manter sessão +iniciada" is the shared `auth_remember_*` module. + +The login-attempt throttle (`config/functions_throttle.php`) stays the app's own, +deliberately not the shared one — see the comment in `config/db.php` for why (avoids +double-counting the same form with two independent throttles). A throttled login feeds +`platform_ip_record_offence()`, so repeat offenders get fleet-wide IP-banned. + +## Soft delete / trash + +Deletes are soft (`deleted_at`/`deleted_by`) via `trash_delete()` in +`config/functions_trash.php`. Gestão → Lixeira lists everything deleted across entity +types and only allows permanent purge after 30 days (enforced in the `DELETE`'s +`WHERE`, not just hidden in the UI). Adding a new deletable entity means adding it to +`trash_types()` in that file. + +## CSRF + +`csrf_check()` (`config/functions_csrf.php`) refuses any non-POST request outright — +state changes must be POST forms carrying `csrf_field()`'s token. There is no PRG +(Post-Redirect-Get) convention beyond that; several admin pages still re-render the +form directly on POST rather than redirecting (audit flags this as inconsistent, not +yet unified — tracked separately, low priority). + +## Audit log + +Models call `audit_row()` before a write and `audit_change()` after (diffs +before/after automatically); ad-hoc events elsewhere use `audit('entity.verb', [...])`. +Both come from `shared/platform-tools` via `config/functions_audit.php`. Read via +Sistema → Auditoria (`platform.php?tool=audit`). + +## Multi-gym + +Since 2026-09-25, one install serves several physical gyms: the `gyms` table, one URL +folder per gym (`/3070/`, `//`), content rows carry `gym_id`, and the +backoffice has an "a editar" gym switcher (`admin_gym_id()` / +`admin_gym()` in `config/functions_gyms.php`). `/` and `/home` 301 to the default gym. +An unpublished gym 404s publicly but previews for staff. + +## platform-tools + +`shared/platform-tools` is a **git submodule** (pinned `branch = main` in +`.gitmodules`), not vendored — see `PLATFORM_TOOLS.md` for why it went vendored→submodule→ +back, and don't repeat that mistake without reading it first. `config/db.php` wires it +via `platform_connect()`: shared auth (above), fleet IP-ban guard (runs on every +request), the sysadmin router (`platform.php` / `api/platform.php` — Backups, +Actualizações, Auditoria, IP bans, Geo, Origem das visitas, all one front controller, +just for *those* pages, not the app's own). + +## Deploy + +Push to `master` → Gitea webhook → `api/platform.php?endpoint=auto_update` → the VPS +pulls, syncs the submodule, applies pending `database/migration_*.sql` files, done in +seconds. No staging branch; the VPS checkout **is** what's live. `deploy.sh` (on the +VPS, not in this repo) is the manual fallback. See `PLATFORM_TOOLS.md`. + +## Dependencies + +`composer.json`/`composer.lock` are real (`composer install` works, `composer +audit`/`outdated` are meaningful) — added 2026-09-25, matching the fotografia +platform's setup. `vendor/PHPMailer/` is the one hand-vendored exception, gitignore- +allowlisted: `shared/platform-tools/functions_mail.php` hard-codes that exact path +fleet-wide, so Composer's own `vendor/phpmailer/phpmailer/` exists in parallel but +isn't what actually gets `require_once`'d. See `ASSETS.md` for frontend asset versions +(two Bootstrap majors, duplicated jQuery — known debt, not yet unified). + +## What's not done (tracked, not silently missing) + +The full audit and its live status live in a separate report, not in this repo +(`CLAUDE_GITEA/audits/fitme3070-auditoria-2026-09-24.html` on the maintainer's +machine). As of 2026-09-25: no real front controller (6.2, above), no unified +create/edit view pairs, no DataTables on backoffice listings, public site still on +Bootstrap 4 while the backoffice runs 5, no `srcset`/responsive images. None of these +are urgent; none should be "fixed" as a side effect of an unrelated change — each is +its own piece of work.