Files
novaconium/novaconium/config.php
T
code d1ce803412 Fix review findings; consolidate pre-release migrations
Bug fixes:
- Deleting a user with comments no longer 500s: remove the user's
  comments first (covers old DBs) and add ON DELETE CASCADE to the FK.
- Drop 'svg' from the default media upload allowlist (stored-XSS vector
  for files served directly from public/uploads/).
- Content index now reindexes on page deletion: track routable page
  count in content_index_meta and treat a count change as stale, since
  the newest-mtime check alone can't see a removal.
- Dev router (public/router.php) preserves the query string across the
  canonical trailing-slash redirect, matching .htaccess.
- Fix stale worked-example reference in the comments thread partial.

Migrations: since v2 is unreleased with no live databases, fold the
incremental ALTERs into the base migrations rather than shipping them
separately: verification columns into 0002_create_users.sql, source_count
into 0001_create_content_index.sql; renumber comments to 0003. Update all
code/doc references to the removed/renamed files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 05:30:10 +00:00

143 lines
7.8 KiB
PHP

<?php
// These are the framework defaults. A project overrides any subset of them
// by creating App/config.php returning an array of just the keys it wants
// to change — novaconium/bootstrap.php shallow-merges it over this file, the
// same App-over-novaconium override pattern used for pages/ and lib/. This
// file itself is not meant to be edited per-project.
return [
// Ordered override roots: App/pages is checked first so a project can
// override any page, sidecar, or layout by placing one at the same
// relative path there; novaconium/pages supplies the framework defaults.
'pages_dirs' => [
__DIR__ . '/../App/pages',
__DIR__ . '/pages',
],
'cache_dir' => __DIR__ . '/../public/cache',
'debug' => true,
// Site name used as the default page title, og:site_name, and the
// footer copyright line in novaconium/pages/_layout/layout.twig.
'site_name' => 'Novaconium Website',
// Matomo analytics. Leave both empty (the default) to disable tracking
// entirely — the layout emits no tracking script at all in that case.
// Set both via App/config.php to enable, e.g.:
// 'matomo_url' => 'https://matomo.example.com/',
// 'matomo_site_id' => '1',
'matomo_url' => '',
'matomo_site_id' => '',
// Gates every /admin/* route (clear-cache, docs, users, and any future
// admin page) behind a session login against the `users` table on
// Lib\Db's default connection — see /admin/docs/admin-auth. The first
// user created is the admin; users after that are 'registered', each
// with an optional group, and see whatever content sidecars grant via
// Lib\Access (see /admin/docs/access-control) — /admin/* itself 404s
// for them. Off by default because it depends on SQLite (same
// reasoning as content_index_enabled below): when false, /admin/* is
// wide open, /admin/login, /admin/logout, and /admin/users 404,
// Access::require() allows everything, and nothing ever touches
// Lib\Db because of this feature. After enabling it via
// App/config.php, create the first user at /admin/users (open access
// until at least one user exists) or with:
// php novaconium/bin/create-admin-user.php <username>
'admin_auth_enabled' => false,
// Lib\Db (see /admin/docs/database) — named, simultaneously-usable
// connections, keyed by name; 'default' is the only one required. A
// sidecar can use more than one at once, e.g. Db::query(...) (default)
// alongside Db::query(..., 'legacy'). Supported drivers: 'sqlite',
// 'mysql'. The default connection's path deliberately lives outside
// both public/ (must never be web-accessible) and novaconium/ (gets
// wholly replaced on a framework update — see
// /admin/docs/getting-started's "Updating the framework" section) — a
// top-level data/ directory, project-owned like App/, is the only safe
// place for it. migrations_dir is optional per connection (omit it to
// never run migrations against that connection, e.g. a read-only
// legacy database) and accepts either one path or an ordered list of
// roots — the default connection lists novaconium/migrations/ (framework
// -shipped schema, e.g. the content index — see /admin/docs/content-index)
// before App/migrations/ (project migrations), so framework migrations
// always apply first. NOTE: unlike every other key here, App/config.php
// merges into db_connections one level deeper than a normal shallow
// override — see the comment on Lib\Db::config() — so adding a second
// connection there doesn't require repeating 'default'.
'db_connections' => [
'default' => [
'driver' => 'sqlite',
'path' => __DIR__ . '/../data/novaconium.sqlite',
'migrations_dir' => [
__DIR__ . '/migrations',
__DIR__ . '/../App/migrations',
],
],
],
// Routes an admin can preview before the public can see them (see
// /admin/docs/drafts) — a list of Route::$dir-format paths, no leading
// slash, e.g. 'blog/upcoming-post'. Not authenticated as admin (per
// AdminAuth::isAuthenticated()) → 404, same as a route that doesn't
// exist at all, so a draft's existence isn't revealed to anyone
// poking at the URL. Authenticated → renders normally, and — critically
// — is never written to the static HTML cache regardless of whether
// the page has a sidecar (see Renderer::render()'s $isDraft param),
// since a world-readable cached copy would otherwise permanently leak
// the draft the first time an admin previewed it.
'draft_routes' => [],
// Content index (see /admin/docs/content-index) — backs /sitemap.xml,
// /search, and blog tag browsing. Off by default: all three depend on
// SQLite (Lib\Db), a real dependency plenty of sites built on this
// framework won't want at all, the same reasoning that keeps Matomo
// and admin auth off by default above. When false, all three routes
// 404 exactly as if they didn't exist, and nothing ever touches
// Lib\Db because of this feature — no data/novaconium.sqlite gets
// created just because the code exists. content_index_auto only
// matters once enabled: true (the default) reindexes lazily,
// on-demand, the first time a stale index is actually needed (never on
// a normal page view); false disables that and leaves indexing
// entirely to `php novaconium/bin/index-content.php`, e.g. from a
// deploy step.
'content_index_enabled' => false,
'content_index_auto' => true,
// Media manager (/admin/media — see /admin/docs/media-manager): an
// upload/browse/delete UI for files under public/uploads/, covered by
// the existing /admin/* auth gate the moment the page exists, so
// there's no separate *_enabled flag here (unlike admin_auth_enabled/
// content_index_enabled above, it has no SQLite dependency to gate).
// media_upload_extensions is an allowlist, matched case-insensitively
// against the uploaded filename's extension; media_upload_max_bytes
// caps a single file's size (checked against both $_FILES' reported
// size and PHP's own upload_max_filesize/post_max_size ini limits,
// see /admin/docs/media-manager). 'svg' is deliberately NOT in this
// default list: files under public/uploads/ are served directly from
// this origin, and an SVG can carry inline <script>, making it a
// stored-XSS vector. Add 'svg' back via App/config.php only if you
// serve uploads with a restrictive CSP or Content-Disposition:
// attachment.
'media_upload_extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'txt', 'zip'],
'media_upload_max_bytes' => 10 * 1024 * 1024,
// Lib\Mailer's transactional-mail driver (see /admin/docs/admin-auth's
// email verification section) — separate from Mailer::send(), the
// contact form's own log-to-file stand-in, which this doesn't touch.
// 'log' (default) writes to the same novaconium/contact-log.txt with no
// external dependency, so a fresh checkout with admin_auth_enabled on
// can still create/verify accounts (via the logged link) with zero
// setup. 'mailjet' sends through MailJet's Send API v3.1
// (https://api.mailjet.com/v3.1/send) using the four keys below — set
// all four via App/config.php, e.g.:
// 'mail_driver' => 'mailjet',
// 'mail_from_email' => 'noreply@example.com',
// 'mail_from_name' => 'Example Site',
// 'mailjet_api_key' => '...',
// 'mailjet_api_secret' => '...',
'mail_driver' => 'log',
'mail_from_email' => '',
'mail_from_name' => '',
'mailjet_api_key' => '',
'mailjet_api_secret' => '',
];