Skip to content

Latest commit

 

History

History
159 lines (117 loc) · 10 KB

File metadata and controls

159 lines (117 loc) · 10 KB

DLTN Framework — Architecture

Request lifecycle

flowchart LR
  publicIndex["public/index.php"] --> bootstrap["core/bootstrap.php"]
  bootstrap --> extensions["content/extensions/{slug}/"]
  extensions --> init["do_action init"]
  init --> routesFile["core/routes.php"]
  routesFile --> routesHook["do_action routes"]
  routesHook --> apiGroup["group /api"]
  apiGroup --> apiFile["core/routes/api.php"]
  apiFile --> apiHook["do_action api_routes"]
  apiHook --> kernel["Kernel::handle"]
  kernel --> reqFilter["apply_filters request"]
  reqFilter --> middleware["middleware pipeline"]
  middleware --> controller["Controller"]
  controller --> view["View Handlebars"]
  view --> resFilter["apply_filters response"]
Loading
  1. Web server serves public/index.php.
  2. Defines DLTN_ROOT, DLTN_CORE, DLTN_CONTENT.
  3. Bootstrap loads Composer autoload, .env + config/*.php, builds the DI Container.
  4. ExtensionLoader discovers each content/extensions/{slug}/ folder with a valid header; enabled ones get templates/ roots, autoload, and bootstrap require. Disabled slugs (Settings extensions.disabled) are skipped.
  5. Fires init.
  6. Registers core routes, then fires routes with the Router.
  7. Registers the /api group (api middleware), loads core/routes/api.php, then fires api_routes.
  8. Returns Kernel. handle() captures the request, applies filters, matches routes, runs middleware, invokes the controller.

Directory map

Path Role
public/ Document root only
core/src/ PHP (Dltn\ PSR-4)
core/templates/ Handlebars templates
core/assets/ CSS/JS (URL /assets/... via PHP)
core/assets/icons/lucide/ Lucide SVG icon set
core/database/migrations/{mysql,sqlite}/ Core/framework SQL per driver (+ optional companion .php)
content/extensions/{slug}/ Self-contained extensions (bootstrap + templates + assets + migrations)
content/extensions/{slug}/database/migrations/{mysql,sqlite}/ Feature schema owned by that extension
content/database/ Default location for SQLite database files
content/templates/ Optional global template overrides
content/uploads/, avatars/, cache/, backups/ Runtime data
config/ Committed PHP config (env() defaults)
.env Secrets + environment overrides (not in git)
docs/ This handbook

Extensions

WordPress-style folders. Preferred bootstrap: content/extensions/{slug}/{slug}.php with an Extension Name header. See EXTENDING.md.

Loaded (enabled) extensions are available from the container as Dltn\Core\ExtensionLoader (all()). All on-disk extensions including disabled: discovered(). Toggle in admin at /admin/extensions (manage_extensions); state stored in Settings key extensions.disabled.

Container bindings (bootstrap)

Registered services include:

  • Config, Database, Encryptor, Session, SessionStore, Csrf, RateLimiter
  • Settings, View, Hooks, ExtensionLoader, Logger, PhpErrorHandler
  • TotpService, AdminAuth, TeamRepository
  • MailerSmtpMailer (reads config['smtp'])
  • Queue (DB-backed jobs; drained by bin/cron.php / bin/queue.php)

Config keys

Key Purpose
db.default Active connection name (mysql or sqlite)
db.connections.* Named connections (driver, credentials / SQLite path, optional prefix)
app_key Encryption at rest
app_secret CSRF / signing
app_url Absolute base URL
app_name Product display name (default DLTN Framework)
app_env production | development
force_https Redirect + secure cookies
smtp.* Outbound mail
session.driver database (default; uses sessions table) or file
session.lifetime Seconds for DB sessions (default 7200)
log.file Optional absolute app log path (default content/logs/app.log)
log.php_file Optional absolute PHP error log path (default content/logs/php.log)
installed Locks installer when true

Database

Supported drivers: MySQL (default) and SQLite. Config selects one via db.default + db.connections. Legacy flat db arrays (no connections) still resolve as MySQL.

Database exposes driver(), table(), quoteIdent(), and upsert() for portable helpers. Raw SQL that names tables should use $db->table('name') when a non-empty prefix is set.

Migrations are per driver with matching basenames:

core/database/migrations/mysql/0001_….sql
core/database/migrations/sqlite/0001_….sql

Schema (core)

Migration 0001_core_schema.sql (core only):

  • team_members — admin users (roles: super_admin, admin, editor, member)
  • team_invites — invite tokens
  • team_email_changes — email change tokens
  • settings — optional key/value JSON
  • rate_limits — login / reset throttling
  • schema_migrations — created by Migrator

Migration 0002_password_resets_jobs_sessions.sql:

  • team_password_resets — forgot-password tokens
  • jobs / job_failures — background queue
  • sessions — DB session store (session.driver = database)

Migration 0003_session_member_meta.sql:

  • Adds team_member_id, ip_address, user_agent on sessions so Account/Team can list devices and revoke sessions remotely

Feature tables belong in extension migrations (content/extensions/{slug}/database/migrations/{driver}/). MigrationRunner applies core first, then each extension (versions stored as ext:{slug}/{file}). Run with php bin/migrate.php (also runs during install).

Admin shell

Routes under /admin with auth middleware. Capabilities via can:view_dashboard, can:edit_content, can:manage_team, can:manage_settings, can:manage_extensions.

Auth extras: /admin/forgot-password, /admin/reset-password/{token}.

JSON API

Core mounts a dedicated /api group (api middleware) from core/routes/api.php. Extensions register under the same prefix via api_routes (router is already scoped to /api).

Method Path Auth Purpose
GET /api/health Public Liveness { "status": "ok" } — works during maintenance and pre-install
GET /api/ready Public Readiness (installed + DB); 200 / 503
GET /api/version Public App name, DLTN_VERSION, PHP version (api.version filter)
GET /api/csrf Public (session) CSRF token for X-CSRF-Token
GET /api/me auth.api Current admin member + capabilities

Middleware aliases for API routes: api, auth.api (JSON 401; session cookie or Authorization: Bearer), csrf (JSON 419 under /api; skipped when a Bearer header is present; also covers PUT/PATCH/DELETE), can:* (JSON 403 under /api). Kernel returns JSON for /api 404/405/500 and maintenance (/api/health exempt).

Super Admins mint and revoke personal API tokens under System Settings → API tokens (can:manage_settings). Tokens are hashed at rest (dltn_…), are owned by a team member (identity for /api/me, revoke-on-deactivate), and carry an explicit capability list chosen at create time — they do not inherit the member’s full role. Capabilities drive can:* for Bearer requests.

Account settings (/admin/account): profile, email, password, avatar, 2FA, and Sessions (list/revoke own devices when using the database session driver). Password change signs out other devices and revokes that member’s API tokens. Team (manage_team) can Sign out everywhere for another member; deactivate/remove also clears their sessions and API tokens. RequireAuth rejects deactivated or missing members even if a cookie remains.

Base nav (then filtered by admin_menu): Dashboard under Menu; Extensions and Team settings under Admin; System Settings under System (manage_settings, Super Admin only). Account settings live in the sidebar user menu. Menu icon values are Lucide names (or raw SVG). Optional link/group section key chooses the sidebar heading (default Menu). Optional order (int, default 50) sorts items within a section — lower first; core items use spaced values (10, 20, …) so extensions can insert with e.g. order => 5.

System Settings (/admin/system, manage_settings): config readout (app_name, app_url, app_env, …), PHP/runtime info, frontend maintenance toggle (creates/removes content/cache/.maintenance; public routes get HTTP 503 + maintenance template for anonymous visitors, fully authenticated admins bypass and can preview the live frontend, /admin, /install, /assets, /ext, /content/avatars, and /content/uploads stay up; admin header shows a Maintenance pill while active), API tokens (create/list/revoke Bearer tokens for any active member), clear template cache, recompile assets (Tailwind → core/assets/css/app.css + Alpine sync; also php bin/build-assets.php), clear logs, and a log viewer with a channel dropdown (Applicationcontent/logs/app.log, PHP errorscontent/logs/php.log). When smtp.host is set, Application shows Send test email (POST /admin/system/test-email) which delivers emails/smtp-test to the signed-in admin. PHP errors/fatals/uncaught exceptions go to the PHP log via PhpErrorHandler. Old /admin/logs redirects there.

Extension assets: /ext/{slug}/...content/extensions/{slug}/assets/... (enabled extensions only). Core assets: /assets/...core/assets/.... Operator files: /content/avatars/... and /content/uploads/... only (never cache/logs/extensions).

Installer

/install/* while installed === false. Steps: welcome → requirements → database → admin account → migrate + write .env → finish. Fires install.complete after success. GET /install/finish remains reachable once so the post-install success page can render after APP_INSTALLED is written; other /install/* paths return 404.

Web server