Files
hyprcrd/src/Database.php
T
2026-04-08 22:46:48 +02:00

131 lines
5.0 KiB
PHP

<?php
class Database
{
private static ?PDO $pdo = null;
public static function connect(): PDO
{
if (self::$pdo === null) {
$dir = dirname(DB_PATH);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
self::$pdo = new PDO('sqlite:' . DB_PATH, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
self::$pdo->exec('PRAGMA journal_mode = WAL');
self::$pdo->exec('PRAGMA foreign_keys = ON');
self::migrate();
}
return self::$pdo;
}
private static function migrate(): void
{
self::$pdo->exec('
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)
');
self::$pdo->exec('
CREATE TABLE IF NOT EXISTS buckets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)
');
self::$pdo->exec('
CREATE TABLE IF NOT EXISTS cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL DEFAULT "",
color TEXT NOT NULL DEFAULT "#ffffff",
bucket_id INTEGER DEFAULT NULL,
position INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (bucket_id) REFERENCES buckets(id) ON DELETE SET NULL
)
');
// Migrations for existing installs
$cols = self::$pdo->query('PRAGMA table_info(cards)')->fetchAll();
$colNames = array_column($cols, 'name');
if (!in_array('bucket_id', $colNames)) {
self::$pdo->exec('ALTER TABLE cards ADD COLUMN bucket_id INTEGER DEFAULT NULL REFERENCES buckets(id) ON DELETE SET NULL');
}
if (!in_array('parent_id', $colNames)) {
self::$pdo->exec('ALTER TABLE cards ADD COLUMN parent_id INTEGER DEFAULT NULL REFERENCES cards(id) ON DELETE SET NULL');
}
if (!in_array('tags', $colNames)) {
self::$pdo->exec("ALTER TABLE cards ADD COLUMN tags TEXT NOT NULL DEFAULT ''");
// Backfill tags from existing card bodies
$all = self::$pdo->query('SELECT id, body FROM cards')->fetchAll();
foreach ($all as $row) {
preg_match_all('/(?<=\s|^)#([a-zA-Z0-9_\/-][a-zA-Z0-9_\/-]*[a-zA-Z0-9_-]|[a-zA-Z0-9_-]+)/m', $row['body'], $matches);
if (!empty($matches[1])) {
$tags = implode(',', array_unique(array_map('strtolower', $matches[1])));
self::$pdo->prepare('UPDATE cards SET tags = :tags WHERE id = :id')
->execute(['tags' => $tags, 'id' => $row['id']]);
}
}
}
if (!in_array('due_date', $colNames)) {
self::$pdo->exec('ALTER TABLE cards ADD COLUMN due_date TEXT DEFAULT NULL');
}
// CalDAV columns
if (!in_array('caldav_uid', $colNames)) {
self::$pdo->exec('ALTER TABLE cards ADD COLUMN caldav_uid TEXT DEFAULT NULL');
self::$pdo->exec('ALTER TABLE cards ADD COLUMN caldav_etag TEXT DEFAULT NULL');
self::$pdo->exec('ALTER TABLE cards ADD COLUMN caldav_href TEXT DEFAULT NULL');
self::$pdo->exec('ALTER TABLE cards ADD COLUMN caldav_synced_at TEXT DEFAULT NULL');
self::$pdo->exec('ALTER TABLE cards ADD COLUMN caldav_rrule TEXT DEFAULT NULL');
}
if (!in_array('caldav_rrule', $colNames) && in_array('caldav_uid', $colNames)) {
self::$pdo->exec('ALTER TABLE cards ADD COLUMN caldav_rrule TEXT DEFAULT NULL');
}
// CalDAV support tables
self::$pdo->exec('
CREATE TABLE IF NOT EXISTS caldav_sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
direction TEXT NOT NULL,
card_id INTEGER,
caldav_uid TEXT,
action TEXT NOT NULL,
status TEXT NOT NULL,
detail TEXT DEFAULT "",
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)
');
self::$pdo->exec('
CREATE TABLE IF NOT EXISTS caldav_tombstones (
caldav_uid TEXT PRIMARY KEY,
card_id INTEGER,
deleted_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)
');
// Bucket color migration
$bcols = self::$pdo->query('PRAGMA table_info(buckets)')->fetchAll();
$bcolNames = array_column($bcols, 'name');
if (!in_array('color', $bcolNames)) {
self::$pdo->exec("ALTER TABLE buckets ADD COLUMN color TEXT NOT NULL DEFAULT '#64748b'");
}
}
}