Fix database migrations for first-time setup. (#29)
Some checks failed
Run unit tests / run-unit-tests (push) Has been cancelled

The database initialization had a number of bugs for the first-time setup. This PR fixes them.

Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/29
Co-authored-by: Greg Sarjeant <greg@subcultureofone.org>
Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
Greg Sarjeant 2025-07-26 15:46:06 +00:00 committed by greg
parent edd7f6effe
commit 4255f46fc7
7 changed files with 19 additions and 91 deletions

View File

@ -1,2 +0,0 @@
ALTER TABLE settings
ADD COLUMN strict_accessibility BOOLEAN DEFAULT TRUE;

View File

@ -15,7 +15,8 @@ CREATE TABLE IF NOT EXISTS settings (
base_url TEXT NOT NULL,
base_path TEXT NOT NULL,
items_per_page INTEGER NOT NULL,
css_id INTEGER NULL
css_id INTEGER NULL,
strict_accessibility BOOLEAN DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS css (

0
config/migrations/002_add_show_tick_mood_setting.sql Executable file → Normal file
View File

View File

@ -14,16 +14,19 @@ if (preg_match('/\.php$/', $path)) {
// Define base paths and load classes
include_once(dirname(dirname(__FILE__)) . "/config/bootstrap.php");
// validate that necessary directories exist and are writable
$fsMgr = new Filesystem();
$fsMgr->validate();
// do any necessary database migrations
$dbMgr = new Database();
$dbMgr->migrate();
// Make sure the initial setup is complete
// unless we're already heading to setup
if (!(preg_match('/setup$/', $path))) {
try {
// filesystem validation
$fsMgr = new Filesystem();
$fsMgr->validate();
// database validation
$dbMgr = new Database();
$dbMgr->validate();
} catch (SetupException $e) {
$e->handle();
@ -31,7 +34,8 @@ if (!(preg_match('/setup$/', $path))) {
}
}
// initialize the database
// Get a database connection
// TODO: Change from static function.
global $db;
$db = Database::get();

View File

@ -1,5 +1,6 @@
<?php
class Database{
// TODO = Make this not static
public static function get(): PDO {
try {
// SQLite will just create this if it doesn't exist.
@ -19,20 +20,16 @@ class Database{
}
public function validate(): void{
$this->validateTables();
$this->validateTableContents();
$this->migrate();
}
// 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.
//
// I'm starting from 0, so if the user_version is NULL, I'll return -1.
private function getVersion(): int {
$db = self::get();
return $db->query("PRAGMA user_version")->fetchColumn() ?? -1;
return $db->query("PRAGMA user_version")->fetchColumn() ?? 0;
}
private function migrationNumberFromFile(string $filename): int {
@ -71,7 +68,7 @@ class Database{
return $pending;
}
private function migrate(): void {
public function migrate(): void {
$migrations = $this->getPendingMigrations();
if (empty($migrations)) {
@ -121,78 +118,6 @@ class Database{
}
}
private function createTables(): void {
$db = self::get();
try {
// user table
$db->exec("CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
display_name TEXT NOT NULL,
password_hash TEXT NULL,
about TEXT NULL,
website TEXT NULL,
mood TEXT NULL
)");
// settings table
$db->exec("CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY,
site_title TEXT NOT NULL,
site_description TEXT NULL,
base_url TEXT NOT NULL,
base_path TEXT NOT NULL,
items_per_page INTEGER NOT NULL,
css_id INTEGER NULL
)");
// css table
$db->exec("CREATE TABLE IF NOT EXISTS css (
id INTEGER PRIMARY KEY,
filename TEXT UNIQUE NOT NULL,
description TEXT NULL
)");
// mood table
$db->exec("CREATE TABLE IF NOT EXISTS emoji(
id INTEGER PRIMARY KEY,
emoji TEXT UNIQUE NOT NULL,
description TEXT NOT NULL
)");
} catch (PDOException $e) {
throw new SetupException(
"Table creation failed: " . $e->getMessage(),
'table_creation',
0,
$e
);
}
}
// make sure all tables exist
// attempt to create them if they don't
private function validateTables(): void {
$appTables = array();
$appTables[] = "settings";
$appTables[] = "user";
$appTables[] = "css";
$appTables[] = "emoji";
$db = self::get();
foreach ($appTables as $appTable){
$stmt = $db->prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?");
$stmt->execute([$appTable]);
if (!$stmt->fetch()){
// At least one table doesn't exist.
// Try creating tables (hacky, but I have 4 total tables)
// Will throw an exception if it fails
$this->createTables();
}
}
}
// make sure tables that need to be seeded have been
private function validateTableContents(): void {
$db = self::get();
@ -201,8 +126,8 @@ class Database{
$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 and we aren't on /admin,
// redirect to /admin to complete setup
// 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",

View File

@ -67,7 +67,7 @@ class ConfigModel {
base_path,
items_per_page,
css_id,
strict_accessibility,
strict_accessibility
)
VALUES (1, ?, ?, ?, ?, ?, ?, ?)");
} else {

View File

@ -30,7 +30,7 @@ class UserModel {
$userCount = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
if ($userCount === 0){
$stmt = $db->prepare("INSERT INTO user (id, username, display_name, website, mood) VALUES (1, ?, ?, ?, ?, ?)");
$stmt = $db->prepare("INSERT INTO user (id, username, display_name, website, mood) VALUES (1, ?, ?, ?, ?)");
} else {
$stmt = $db->prepare("UPDATE user SET username=?, display_name=?, website=?, mood=? WHERE id=1");
}