feat(dev): adopt shared platform-tools dev toolbar and one-click dev login
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c847267ea
commit
4ed2dd3709
7 files changed
+76
-76
No files matched your search
@@ -58,6 +58,9 @@ RewriteRule ^site/assets/ - [L]
|
||||
RewriteRule ^assets/ - [L]
|
||||
|
||||
# ── Friendly URLs ─────────────────────────────────────────────────────────────
|
||||
# DEV ONLY: dev_login.php 404s sozinho se o platform-tools não tiver functions_dev.php.
|
||||
RewriteCond %{REQUEST_FILENAME}.php -f
|
||||
RewriteRule ^dev_login/?$ dev_login.php [L]
|
||||
RewriteRule ^home/?$ site/index.php [L]
|
||||
RewriteRule ^home/equipa/?$ site/equipa.php [L,QSA]
|
||||
RewriteRule ^home/modalidades/?$ site/modalidades.php [L,QSA]
|
||||
|
||||
@@ -132,3 +132,8 @@ or adapted for production, and these accounts must never exist in a production d
|
||||
## Brand feed (2026-10-09)
|
||||
- `api/brand.php` is a one-line stub to `shared/platform-tools/api/brand.php` (public JSON: name, logos, colours; README "Brand feed"). Declared in `platform_connect(['brand' => ...])` in `config/db.php`; colours are the `:root` tokens (`--fitme-accent` #c2d500, `--fitme-dark` #0b0b0d, `--fitme-accent-dark` #a6b600). `logo_light` = light-coloured logo (`logo_crossfit_white.svg`), `logo_dark` = `logo_crossfit.svg`.
|
||||
- `site_logo_rel()` (config/functions.php) resolves logos for both the feed and `load_email_template()`: setting file in `uploads/public/logo/` (convention), then legacy `assets/img/logo/`, then the static logo. `views/sysadmin/system_settings.php` now uploads logos to `uploads/public/logo/` (dir created on demand, 0755; `uploads/.htaccess` blocks script execution); old files in `assets/img/logo/` still resolve as fallback and in the settings preview. Brand block in `config/db.php` already prefers the setting file via `site_logo_rel()`, else the static SVGs.
|
||||
|
||||
## Dev toolbar partilhada (2026-10-09)
|
||||
- DEV ONLY, do platform-tools (`functions_dev.php`, README "Dev tooling"; produção segue `main`, que não tem o ficheiro: tudo degrada para "não é dev").
|
||||
- Ficheiros: `config/dev_guard.php` (shim), `includes/dev_toolbar.php` (incluído no fim de `includes/footer.php`), `dev_login.php` (na RAIZ: o site não tem `public/`; `.htaccess` tem a regra `^dev_login/?$` com `-f`), `scripts/dev_accounts.php` (`platform_dev_accounts_run`).
|
||||
- Contas `admin@mail.pt` / `sysadmin@mail.pt` (pw `123`). O login usa `auth_login()` + `fit_stamp_login_ua()` como o login real; CSRF do site = campo `_csrf` / `$_SESSION['csrf_token']` (verificação sem `exit`).
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
// DEV ONLY. A definição de «ambiente de desenvolvimento» é a do platform-tools
|
||||
// (functions_dev.php). Carregado em todas as páginas (rodapé): se o submódulo
|
||||
// ainda não tem o ficheiro (produção segue o main do platform-tools), degrada
|
||||
// para «não é dev» em vez de dar fatal.
|
||||
$_dev_shared = dirname(__DIR__) . '/shared/platform-tools/functions_dev.php';
|
||||
if (is_file($_dev_shared)) require_once $_dev_shared;
|
||||
unset($_dev_shared);
|
||||
|
||||
if (!function_exists('dev_environment_problem')) {
|
||||
function dev_environment_problem(?array $env = null): ?string {
|
||||
return function_exists('platform_dev_environment_problem')
|
||||
? platform_dev_environment_problem($env) : 'platform-tools sem functions_dev.php';
|
||||
}
|
||||
}
|
||||
if (!function_exists('dev_environment_ok')) {
|
||||
function dev_environment_ok(?array $env = null): bool { return dev_environment_problem($env) === null; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* DEV ONLY -- login de um clique da barra de dev (includes/dev_toolbar.php).
|
||||
* Fica na raiz porque este site não tem public/ (o .htaccess encaminha
|
||||
* /dev_login para aqui). O guarda, o 404 antes de tocar na BD/sessão, o POST e o
|
||||
* papel permitido são do platform-tools (platform_dev_login_handle). Aqui só: o
|
||||
* CSRF do site e a abertura da sessão como o login real (auth_login), sem
|
||||
* palavra-passe. As contas são as de scripts/dev_accounts.php.
|
||||
*/
|
||||
$_dev_shared = __DIR__ . '/shared/platform-tools/functions_dev.php';
|
||||
if (!is_file($_dev_shared)) { http_response_code(404); exit; } // submódulo sem o ficheiro (produção)
|
||||
require_once $_dev_shared;
|
||||
|
||||
platform_dev_login_handle([
|
||||
'roles' => ['admin' => 'admin@mail.pt', 'sysadmin' => 'sysadmin@mail.pt'],
|
||||
'bootstrap' => function () { global $pdo; require __DIR__ . '/config/db.php'; require_once __DIR__ . '/config/functions.php'; },
|
||||
'csrf_ok' => fn(): bool => is_string($_POST['_csrf'] ?? null) && is_string($_SESSION['csrf_token'] ?? null)
|
||||
&& $_SESSION['csrf_token'] !== '' && hash_equals($_SESSION['csrf_token'], $_POST['_csrf']),
|
||||
'login' => function (string $role, string $email): ?string {
|
||||
global $pdo;
|
||||
$st = $pdo->prepare('SELECT * FROM users WHERE email = ? AND role = ? AND is_active = 1 LIMIT 1');
|
||||
$st->execute([$email, $role]);
|
||||
$user = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$user) return null; // conta em falta: corra php scripts/dev_accounts.php
|
||||
auth_login($pdo, $user);
|
||||
fit_stamp_login_ua($pdo, (int) $user['id']);
|
||||
return base_url(post_login_destination($role));
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// DEV ONLY -- barra de login de um clique (platform-tools). Não imprime nada
|
||||
// fora de dev. Incluída por includes/footer.php.
|
||||
require_once __DIR__ . '/../config/dev_guard.php';
|
||||
if (!function_exists('platform_dev_toolbar')) return;
|
||||
platform_dev_toolbar([
|
||||
'login_url' => base_url('dev_login'),
|
||||
'logout_url' => base_url('views/auth/logout.php'),
|
||||
'roles' => ['admin' => 'Admin', 'sysadmin' => 'Sysadmin'],
|
||||
'csrf_field' => 'csrf_field',
|
||||
]);
|
||||
@@ -105,5 +105,6 @@ main:after {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
<?php include __DIR__ . '/dev_toolbar.php'; ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,81 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* DEV ONLY -- idempotent upsert of the two standing test logins:
|
||||
* sysadmin@mail.pt (role sysadmin) admin@mail.pt (role admin) password: 123
|
||||
*
|
||||
* CLI only. Fails closed: refuses unless APP_ENV is a development value AND the
|
||||
* DB host is the dev server (192.168.1.3:3181) or localhost. Never run in prod.
|
||||
* DEV ONLY -- upsert idempotente das contas de teste fixas (sysadmin@mail.pt e
|
||||
* admin@mail.pt, palavra-passe 123). Só CLI. A lógica e o guarda (APP_ENV dev E
|
||||
* BD 192.168.1.3:3181, ou localhost com DEV_ALLOW_LOCAL=1) são do platform-tools.
|
||||
* php scripts/dev_accounts.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(404); exit; }
|
||||
|
||||
// ---- per-project schema (edit only this block) -----------------------------
|
||||
$T = [
|
||||
'table' => 'users', 'id' => 'id', 'email' => 'email', 'name' => 'name',
|
||||
'password' => 'password_hash', 'role' => 'role',
|
||||
'flags' => ['is_active' => 1], // column => value that lets login work (insert AND update)
|
||||
'extra' => [], // column => value for new rows only; '{email}' is substituted
|
||||
'roles' => ['sysadmin@mail.pt' => 'sysadmin', 'admin@mail.pt' => 'admin'],
|
||||
];
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const DEV_PASSWORD = '123';
|
||||
function dev_fail(string $m): never { fwrite(STDERR, "REFUSED: $m\n"); exit(1); }
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
$envFile = $root . '/.env';
|
||||
if (!is_file($envFile)) dev_fail('.env not found');
|
||||
$env = [];
|
||||
foreach (file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $l) {
|
||||
$l = trim($l);
|
||||
if ($l === '' || $l[0] === '#' || !str_contains($l, '=')) continue;
|
||||
[$k, $v] = explode('=', $l, 2);
|
||||
$v = trim($v);
|
||||
if ($v !== '' && $v[0] !== '"' && $v[0] !== "'" && ($h = strpos($v, ' #')) !== false) $v = rtrim(substr($v, 0, $h));
|
||||
$env[trim($k)] = trim($v, " \"'");
|
||||
}
|
||||
foreach (array_keys($env) as $k) { if (($r = getenv($k)) !== false) $env[$k] = $r; } // real env wins, like the app
|
||||
|
||||
$appEnv = strtolower($env['APP_ENV'] ?? '');
|
||||
if (!in_array($appEnv, ['dev', 'development', 'local'], true)) dev_fail('APP_ENV is not dev');
|
||||
$host = strtolower($env['DB_HOST'] ?? ''); $port = (int) ($env['DB_PORT'] ?? 3306);
|
||||
$ok = in_array($host, ['localhost', '127.0.0.1', '::1'], true) || ($host === '192.168.1.3' && $port === 3181);
|
||||
if (!$ok) dev_fail('DB host is not the dev server');
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;port=$port;dbname={$env['DB_NAME']};charset=utf8mb4", $env['DB_USER'] ?? '', $env['DB_PASS'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
} catch (Throwable $e) { dev_fail('DB connect failed'); }
|
||||
|
||||
$q = fn(string $c) => '`' . str_replace('`', '', $c) . '`';
|
||||
$cols = $pdo->prepare('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?');
|
||||
$cols->execute([$T['table']]);
|
||||
$have = $cols->fetchAll(PDO::FETCH_COLUMN);
|
||||
$need = array_merge([$T['email'], $T['password'], $T['role']], array_keys($T['flags']), array_keys($T['extra']));
|
||||
if ($T['name']) $need[] = $T['name'];
|
||||
$missing = array_diff($need, $have);
|
||||
if (!$have) dev_fail("table {$T['table']} missing (schema behind)");
|
||||
if ($missing) dev_fail('schema behind, missing columns: ' . implode(', ', $missing));
|
||||
|
||||
$hash = password_hash(DEV_PASSWORD, PASSWORD_DEFAULT);
|
||||
foreach ($T['roles'] as $email => $role) {
|
||||
$set = [$T['password'] => $hash, $T['role'] => $role] + $T['flags'];
|
||||
$s = $pdo->prepare("SELECT {$q($T['id'])} FROM {$q($T['table'])} WHERE {$q($T['email'])} = ? LIMIT 1");
|
||||
$s->execute([$email]);
|
||||
$id = $s->fetchColumn();
|
||||
if ($id !== false) {
|
||||
$sql = "UPDATE {$q($T['table'])} SET " . implode(', ', array_map(fn($c) => $q($c) . ' = ?', array_keys($set)))
|
||||
. " WHERE {$q($T['id'])} = ?";
|
||||
$pdo->prepare($sql)->execute([...array_values($set), $id]);
|
||||
echo "updated $email ($role)\n";
|
||||
} else {
|
||||
$set[$T['email']] = $email;
|
||||
if ($T['name']) $set[$T['name']] = ucfirst($role) . ' (dev)';
|
||||
foreach ($T['extra'] as $c => $v) $set[$c] = is_string($v) ? str_replace('{email}', $email, $v) : $v;
|
||||
$sql = "INSERT INTO {$q($T['table'])} (" . implode(', ', array_map($q, array_keys($set))) . ') VALUES ('
|
||||
. implode(', ', array_fill(0, count($set), '?')) . ')';
|
||||
$pdo->prepare($sql)->execute(array_values($set));
|
||||
echo "created $email ($role)\n";
|
||||
}
|
||||
}
|
||||
$_dev_shared = dirname(__DIR__) . '/shared/platform-tools/functions_dev.php';
|
||||
if (!is_file($_dev_shared)) { fwrite(STDERR, "REFUSED: platform-tools sem functions_dev.php\n"); exit(1); }
|
||||
require_once $_dev_shared;
|
||||
exit(platform_dev_accounts_run(['flags' => ['is_active' => 1]]));
|
||||
Reference in new issue
Block a user