Add database migration. Add accessibility and mood settings. Add mood to tick format.
This commit is contained in:
parent
d6673c7ed2
commit
efe9688289
@ -66,6 +66,8 @@ function confirm_setup(): void {
|
|||||||
validate_storage_subdirs();
|
validate_storage_subdirs();
|
||||||
validate_tables();
|
validate_tables();
|
||||||
validate_table_contents();
|
validate_table_contents();
|
||||||
|
migrate_db();
|
||||||
|
migrate_tick_files();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the storage/ directory exists and is writable
|
// Make sure the storage/ directory exists and is writable
|
||||||
@ -114,6 +116,38 @@ function validate_storage_subdirs(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function migrate_tick_files() {
|
||||||
|
$files = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator(TICKS_DIR, RecursiveDirectoryIterator::SKIP_DOTS)
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file->isFile() && str_ends_with($file->getFilename(), '.txt')) {
|
||||||
|
migrate_tick_file($file->getPathname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrate_tick_file($filepath) {
|
||||||
|
$lines = file($filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
|
$modified = false;
|
||||||
|
|
||||||
|
foreach ($lines as &$line) {
|
||||||
|
$fields = explode('|', $line);
|
||||||
|
if (count($fields) === 2) {
|
||||||
|
// Convert id|text to id|emoji|text
|
||||||
|
$line = $fields[0] . '||' . $fields[1];
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($modified) {
|
||||||
|
file_put_contents($filepath, implode("\n", $lines) . "\n");
|
||||||
|
// TODO: log properly
|
||||||
|
//echo "Migrated: " . basename($filepath) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function get_db(): PDO {
|
function get_db(): PDO {
|
||||||
try {
|
try {
|
||||||
// SQLite will just create this if it doesn't exist.
|
// SQLite will just create this if it doesn't exist.
|
||||||
@ -132,6 +166,93 @@ function get_db(): PDO {
|
|||||||
return $db;
|
return $db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
function get_db_version(): int {
|
||||||
|
$db = get_db();
|
||||||
|
|
||||||
|
return $db->query("PRAGMA user_version")->fetchColumn() ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function migration_number_from_file(string $filename): int {
|
||||||
|
$basename = basename($filename, '.sql');
|
||||||
|
$parts = explode('_', $basename);
|
||||||
|
return (int) $parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_db_version(int $newVersion): void {
|
||||||
|
$currentVersion = get_db_version();
|
||||||
|
|
||||||
|
if ($newVersion <= $currentVersion){
|
||||||
|
throw new SetupException(
|
||||||
|
"New version ($newVersion) must be greater than current version ($currentVersion)",
|
||||||
|
'db_migration'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = get_db();
|
||||||
|
$db->exec("PRAGMA user_version = $newVersion");
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_pending_migrations(): array {
|
||||||
|
$currentVersion = get_db_version();
|
||||||
|
$files = glob(DATA_DIR . '/migrations/*.sql');
|
||||||
|
|
||||||
|
$pending = [];
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$version = migration_number_from_file($file);
|
||||||
|
if ($version > $currentVersion) {
|
||||||
|
$pending[$version] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($pending);
|
||||||
|
return $pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrate_db(): void {
|
||||||
|
$migrations = get_pending_migrations();
|
||||||
|
|
||||||
|
if (empty($migrations)) {
|
||||||
|
# TODO: log
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = get_db();
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($migrations as $version => $file) {
|
||||||
|
$filename = basename($file);
|
||||||
|
// TODO: log properly
|
||||||
|
|
||||||
|
$sql = file_get_contents($file);
|
||||||
|
if ($sql === false) {
|
||||||
|
throw new Exception("Could not read migration file: $file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the migration SQL
|
||||||
|
$db->exec($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update db version
|
||||||
|
$db->commit();
|
||||||
|
set_db_version($version);
|
||||||
|
//TODO: log properly
|
||||||
|
//echo "All migrations completed successfully.\n";
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
throw new SetupException(
|
||||||
|
"Migration failed: $filename",
|
||||||
|
'db_migration',
|
||||||
|
0,
|
||||||
|
$e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function create_tables(): void {
|
function create_tables(): void {
|
||||||
$db = get_db();
|
$db = get_db();
|
||||||
|
|
||||||
|
@ -72,6 +72,16 @@ select {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: auto;
|
||||||
|
height: 2rem;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
justify-self: start;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* A bit of custom styling for the file input */
|
/* A bit of custom styling for the file input */
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
@ -429,6 +439,13 @@ time {
|
|||||||
- Once the width exceeds that (e.g. desktops), it will convert to horizontal alignment
|
- Once the width exceeds that (e.g. desktops), it will convert to horizontal alignment
|
||||||
*/
|
*/
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
|
input[type="checkbox"] {
|
||||||
|
height: 100%;
|
||||||
|
/*grid-column: 2;*/
|
||||||
|
justify-self: start;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
@ -52,22 +52,24 @@ class AdminController extends Controller {
|
|||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
// UserModel profile
|
// User profile
|
||||||
$username = trim($_POST['username'] ?? '');
|
$username = trim($_POST['username'] ?? '');
|
||||||
$displayName = trim($_POST['display_name'] ?? '');
|
$displayName = trim($_POST['display_name'] ?? '');
|
||||||
$about = trim($_POST['about'] ?? '');
|
$about = trim($_POST['about'] ?? '');
|
||||||
$website = trim($_POST['website'] ?? '');
|
$website = trim($_POST['website'] ?? '');
|
||||||
|
|
||||||
// Site settings
|
// Site settings
|
||||||
$siteTitle = trim($_POST['site_title']) ?? '';
|
$siteTitle = trim($_POST['site_title']) ?? '';
|
||||||
$siteDescription = trim($_POST['site_description']) ?? '';
|
$siteDescription = trim($_POST['site_description']) ?? '';
|
||||||
$baseUrl = trim($_POST['base_url'] ?? '');
|
$baseUrl = trim($_POST['base_url'] ?? '');
|
||||||
$basePath = trim($_POST['base_path'] ?? '/');
|
$basePath = trim($_POST['base_path'] ?? '/');
|
||||||
$itemsPerPage = (int) ($_POST['items_per_page'] ?? 25);
|
$itemsPerPage = (int) ($_POST['items_per_page'] ?? 25);
|
||||||
|
$strictAccessibility = isset($_POST['strict_accessibility']);
|
||||||
|
$showTickMood = isset($_POST['strict_accessibility']);
|
||||||
|
|
||||||
// Password
|
// Password
|
||||||
$password = $_POST['password'] ?? '';
|
$password = $_POST['password'] ?? '';
|
||||||
$confirmPassword = $_POST['confirm_password'] ?? '';
|
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||||
|
|
||||||
// Validate user profile
|
// Validate user profile
|
||||||
if (!$username) {
|
if (!$username) {
|
||||||
@ -112,6 +114,8 @@ class AdminController extends Controller {
|
|||||||
$config->baseUrl = $baseUrl;
|
$config->baseUrl = $baseUrl;
|
||||||
$config->basePath = $basePath;
|
$config->basePath = $basePath;
|
||||||
$config->itemsPerPage = $itemsPerPage;
|
$config->itemsPerPage = $itemsPerPage;
|
||||||
|
$config->strictAccessibility = $strictAccessibility;
|
||||||
|
$config->showTickMood = $showTickMood;
|
||||||
|
|
||||||
// Save site settings and reload config from database
|
// Save site settings and reload config from database
|
||||||
// TODO - raise and handle exception on failure
|
// TODO - raise and handle exception on failure
|
||||||
|
@ -8,6 +8,8 @@ class ConfigModel {
|
|||||||
public int $itemsPerPage = 25;
|
public int $itemsPerPage = 25;
|
||||||
public string $timezone = 'relative';
|
public string $timezone = 'relative';
|
||||||
public ?int $cssId = null;
|
public ?int $cssId = null;
|
||||||
|
public bool $strictAccessibility = true;
|
||||||
|
public bool $showTickMood = true;
|
||||||
|
|
||||||
// load config from sqlite database
|
// load config from sqlite database
|
||||||
public static function load(): self {
|
public static function load(): self {
|
||||||
@ -17,7 +19,16 @@ class ConfigModel {
|
|||||||
$c->basePath = ($c->basePath === '') ? $init['base_path'] : $c->basePath;
|
$c->basePath = ($c->basePath === '') ? $init['base_path'] : $c->basePath;
|
||||||
|
|
||||||
global $db;
|
global $db;
|
||||||
$stmt = $db->query("SELECT site_title, site_description, base_url, base_path, items_per_page, css_id FROM settings WHERE id=1");
|
$stmt = $db->query("SELECT site_title,
|
||||||
|
site_description,
|
||||||
|
base_url,
|
||||||
|
base_path,
|
||||||
|
items_per_page,
|
||||||
|
css_id,
|
||||||
|
strict_accessibility,
|
||||||
|
show_tick_mood
|
||||||
|
FROM settings WHERE id=1");
|
||||||
|
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($row) {
|
if ($row) {
|
||||||
@ -26,7 +37,8 @@ class ConfigModel {
|
|||||||
$c->baseUrl = $row['base_url'];
|
$c->baseUrl = $row['base_url'];
|
||||||
$c->basePath = $row['base_path'];
|
$c->basePath = $row['base_path'];
|
||||||
$c->itemsPerPage = (int) $row['items_per_page'];
|
$c->itemsPerPage = (int) $row['items_per_page'];
|
||||||
$c->cssId = (int) $row['css_id'];
|
$c->strictAccessibility = (bool) $row['strict_accessibility'];
|
||||||
|
$c->showTickMood = (bool) $row['show_tick_mood'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $c;
|
return $c;
|
||||||
@ -49,11 +61,39 @@ class ConfigModel {
|
|||||||
$settingsCount = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn();
|
$settingsCount = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn();
|
||||||
|
|
||||||
if ($settingsCount === 0){
|
if ($settingsCount === 0){
|
||||||
$stmt = $db->prepare("INSERT INTO settings (id, site_title, site_description, base_url, base_path, items_per_page, css_id) VALUES (1, ?, ?, ?, ?, ?, ?)");
|
$stmt = $db->prepare("INSERT INTO settings (
|
||||||
|
id,
|
||||||
|
site_title,
|
||||||
|
site_description,
|
||||||
|
base_url,
|
||||||
|
base_path,
|
||||||
|
items_per_page,
|
||||||
|
css_id,
|
||||||
|
strict_accessibility,
|
||||||
|
show_tick_mood
|
||||||
|
)
|
||||||
|
VALUES (1, ?, ?, ?, ?, ?, ?)");
|
||||||
} else {
|
} else {
|
||||||
$stmt = $db->prepare("UPDATE settings SET site_title=?, site_description=?, base_url=?, base_path=?, items_per_page=?, css_id=? WHERE id=1");
|
$stmt = $db->prepare("UPDATE settings SET
|
||||||
|
site_title=?,
|
||||||
|
site_description=?,
|
||||||
|
base_url=?,
|
||||||
|
base_path=?,
|
||||||
|
items_per_page=?,
|
||||||
|
css_id=?,
|
||||||
|
strict_accessibility=?,
|
||||||
|
show_tick_mood=?
|
||||||
|
WHERE id=1");
|
||||||
}
|
}
|
||||||
$stmt->execute([$this->siteTitle, $this->siteDescription, $this->baseUrl, $this->basePath, $this->itemsPerPage, $this->cssId]);
|
$stmt->execute([$this->siteTitle,
|
||||||
|
$this->siteDescription,
|
||||||
|
$this->baseUrl,
|
||||||
|
$this->basePath,
|
||||||
|
$this->itemsPerPage,
|
||||||
|
$this->cssId,
|
||||||
|
$this->strictAccessibility,
|
||||||
|
$this->showTickMood
|
||||||
|
]);
|
||||||
|
|
||||||
return self::load();
|
return self::load();
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,7 @@ class TickModel {
|
|||||||
|
|
||||||
// Ticks are pipe-delimited: timestamp|text
|
// Ticks are pipe-delimited: timestamp|text
|
||||||
// But just in case a tick contains a pipe, only split on the first one that occurs
|
// But just in case a tick contains a pipe, only split on the first one that occurs
|
||||||
$tickParts = explode('|', $line, 2);
|
list($time, $emoji, $tick) = explode('|', $line, 3);
|
||||||
$time = $tickParts[0];
|
|
||||||
$tick = $tickParts[1];
|
|
||||||
|
|
||||||
// Build the timestamp from the date and time
|
// Build the timestamp from the date and time
|
||||||
// Ticks are always stored in UTC
|
// Ticks are always stored in UTC
|
||||||
@ -90,10 +88,13 @@ class TickModel {
|
|||||||
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
if (str_starts_with($line, $timestamp)) {
|
if (str_starts_with($line, $timestamp)) {
|
||||||
$tick = explode('|', $line)[1];
|
echo $line;
|
||||||
|
exit;
|
||||||
|
list($time, $emoji, $tick) = explode('|', $line, 3);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'tickTime' => $tickTime,
|
'tickTime' => $tickTime,
|
||||||
|
'emoji' => $emoji,
|
||||||
'tick' => $tick,
|
'tick' => $tick,
|
||||||
'config' => ConfigModel::load(),
|
'config' => ConfigModel::load(),
|
||||||
];
|
];
|
||||||
|
2
storage/db/migrations/001_add_accessibility_setting.sql
Executable file
2
storage/db/migrations/001_add_accessibility_setting.sql
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE settings
|
||||||
|
ADD COLUMN strict_accessibility BOOLEAN DEFAULT TRUE;
|
2
storage/db/migrations/002_add_show_tick_mood_setting.sql
Executable file
2
storage/db/migrations/002_add_show_tick_mood_setting.sql
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE settings
|
||||||
|
ADD COLUMN show_tick_mood BOOLEAN DEFAULT TRUE;
|
BIN
storage/db/tkr.sqlite.bak
Executable file
BIN
storage/db/tkr.sqlite.bak
Executable file
Binary file not shown.
@ -57,6 +57,18 @@
|
|||||||
name="items_per_page"
|
name="items_per_page"
|
||||||
value="<?= $config->itemsPerPage ?>" min="1" max="50"
|
value="<?= $config->itemsPerPage ?>" min="1" max="50"
|
||||||
required>
|
required>
|
||||||
|
<label>Strict accessibility</label>
|
||||||
|
<input type="checkbox"
|
||||||
|
id="strict_accessibility"
|
||||||
|
name="strict_accessibility"
|
||||||
|
value="1"
|
||||||
|
<?php if ($config->strictAccessibility): ?> checked <?php endif; ?>>
|
||||||
|
<label>Show tick mood</label>
|
||||||
|
<input type="checkbox"
|
||||||
|
id="show_tick_mood"
|
||||||
|
name="show_tick_mood"
|
||||||
|
value="1"
|
||||||
|
<?php if ($config->showTickMood): ?> checked <?php endif; ?>>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user