diff --git a/public/index.php b/public/index.php index 8f7f214..0882d52 100644 --- a/public/index.php +++ b/public/index.php @@ -1,4 +1,8 @@ validate(); if (count($prerequisites->getErrors()) > 0) { @@ -22,28 +30,46 @@ if (count($prerequisites->getErrors()) > 0) { exit; } -// Do any necessary database migrations -$dbMgr = new Database(); -$dbMgr->migrate(); - -// Make sure the initial setup is complete -// unless we're already heading to setup -// -// TODO: Consider simplifying this. -// Might not need the custom exception now that the prereq checker is more robust. -if (!(preg_match('/setup$/', $path))) { - try { - // Make sure setup has been completed - $dbMgr->confirmSetup(); - } catch (SetupException $e) { - $e->handle(); - exit; - } +// Connect to the database +try { + // SQLite will just create this if it doesn't exist. + $db = new PDO("sqlite:" . DB_FILE); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); +} catch (PDOException $e) { + throw new SetupException( + "Database connection failed: " . $e->getMessage(), + 'database_connection', + 0, + $e + ); } +// Do any necessary database migrations +$migrator = new Migrator($db); +$migrator->migrate(); + +// Make sure the initial setup is complete unless we're already heading to setup +if (!(preg_match('/setup$/', $path))) { + // Make sure required tables (user, settings) are populated + $user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn(); + $settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn(); + + // If either required table has no records, redirect to setup. + if ($user_count === 0 || $settings_count === 0){ + $init = require APP_ROOT . '/config/init.php'; + header('Location: ' . $init['base_path'] . 'setup'); + exit; + }; +} + +/* + * Begin processing request + */ + // Initialize application context with all dependencies global $app; -$db = Database::get(); + $app = [ 'db' => $db, 'config' => (new ConfigModel($db))->loadFromDatabase(), @@ -51,7 +77,6 @@ $app = [ ]; // Start a session and generate a CSRF Token -// if there isn't already an active session Session::start(); Session::generateCsrfToken(); @@ -75,7 +100,7 @@ if ($method === 'POST' && $path != 'setup') { if (!Session::isValid($_POST['csrf_token'])) { // Invalid session - redirect to /login Log::info('Attempt to POST with invalid session. Redirecting to login.'); - header('Location: ' . Util::buildRelativeUrl($config->basePath, 'login')); + header('Location: ' . Util::buildRelativeUrl($app->config->basePath, 'login')); exit; } } else { diff --git a/src/Framework/Exception/SetupException.php b/src/Framework/Exception/SetupException.php index 0bf0c54..befbfd7 100644 --- a/src/Framework/Exception/SetupException.php +++ b/src/Framework/Exception/SetupException.php @@ -20,6 +20,8 @@ class SetupException extends Exception { // We don't want to short-circuit this if there's a problem logging } + // TODO: This doesn't need to be a switch anymore + // May not need to exist at all switch ($this->setupIssue){ case 'database_connection': case 'db_migration': @@ -29,19 +31,6 @@ class SetupException extends Exception { echo "

Configuration Error

"; echo "

" . Util::escape_html($this->setupIssue) . '-' . Util::escape_html($this->getMessage()) . "

"; exit; - case 'table_contents': - // Recoverable error. - // Redirect to setup if we aren't already headed there. - // NOTE: Just read directly from init.php instead of - // trying to use the config object. This is the initial - // setup. It shouldn't assume any data can be loaded. - $init = require APP_ROOT . '/config/init.php'; - $currentPath = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/'); - - if (strpos($currentPath, 'setup') === false) { - header('Location: ' . $init['base_path'] . 'setup'); - exit; - } } } diff --git a/src/Framework/Database/Database.php b/src/Framework/Migrator/Migrator.php similarity index 59% rename from src/Framework/Database/Database.php rename to src/Framework/Migrator/Migrator.php index 14cf62b..32d2d6c 100644 --- a/src/Framework/Database/Database.php +++ b/src/Framework/Migrator/Migrator.php @@ -1,35 +1,11 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); - } catch (PDOException $e) { - throw new SetupException( - "Database connection failed: " . $e->getMessage(), - 'database_connection', - 0, - $e - ); - } +class Migrator{ + public function __construct(private PDO $db) {} - return $db; - } - - public function validate(): void{ - $this->validateTableContents(); - } - - // The database version will just be an int - // stored as PRAGMA user_version. It will - // correspond to the most recent migration file applied to the db. + // The database version is an int stored as PRAGMA user_version. + // It corresponds to the most recent migration file applied to the db. private function getVersion(): int { - $db = self::get(); - - return $db->query("PRAGMA user_version")->fetchColumn() ?? 0; + return $this->db->query("PRAGMA user_version")->fetchColumn() ?? 0; } private function migrationNumberFromFile(string $filename): int { @@ -48,8 +24,7 @@ class Database{ ); } - $db = self::get(); - $db->exec("PRAGMA user_version = $newVersion"); + $this->db->exec("PRAGMA user_version = $newVersion"); } private function getPendingMigrations(): array { @@ -79,8 +54,7 @@ class Database{ Log::info("Found " . count($migrations) . " pending migrations."); Log::info("Updating database. Current Version: " . $this->getVersion()); - $db = self::get(); - $db->beginTransaction(); + $this->db->beginTransaction(); try { foreach ($migrations as $version => $file) { @@ -100,7 +74,7 @@ class Database{ foreach ($statements as $statement){ if (!empty($statement)){ Log::debug("Migration statement: {$statement}"); - $db->exec($statement); + $this->db->exec($statement); } } @@ -108,13 +82,13 @@ class Database{ } // Update db version - $db->commit(); + $this->db->commit(); $this->setVersion($version); Log::info("Applied " . count($migrations) . " migrations."); Log::info("Updated database version to " . $this->getVersion()); } catch (Exception $e) { - $db->rollBack(); + $this->db->rollBack(); throw new SetupException( "Migration failed: $filename", 'db_migration', @@ -123,22 +97,4 @@ class Database{ ); } } - - // make sure tables that need to be seeded have been - public function confirmSetup(): void { - $db = self::get(); - - // make sure required tables (user, settings) are populated - $user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn(); - $settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn(); - - // If either required table has no records, throw an exception. - // This will be caught and redirect to setup. - if ($user_count === 0 || $settings_count === 0){ - throw new SetupException( - "Required tables aren't populated. Please complete setup", - 'table_contents', - ); - }; - } } \ No newline at end of file