Files
protocol-droid/api/db.php
T
Protocolbot 3386ac6542 feat: switch from MySQL to SQLite for zero-dependency deployment
- Rewrite db.php to use SQLite via PDO (no external DB server needed)
- Rewrite schema.sql for SQLite (AUTOINCREMENT, CHECK constraints,
  triggers for updated_at, ON CONFLICT instead of ON DUPLICATE KEY)
- Fix config.php upsert to use ON CONFLICT syntax
- Fix index.php: tag filter via json_each, vote upsert via ON CONFLICT,
  and fix protocol create route (was returning 405 for POST /api/protocols)
- Fix yaml.php seed import: replace invalid false callbacks with fn() => null,
  fix yaml_parse pos argument (-1 -> 0)
- Update Dockerfile: drop pdo_mysql/mysqli (pdo_sqlite is built in)
- Update CloudronManifest.json: remove mysql addon, bump to v0.3.0
- Update README for SQLite deployment
2026-07-06 14:42:45 -06:00

96 lines
2.9 KiB
PHP

<?php
/**
* Database connection and helpers.
* Uses SQLite via PDO — no external database server required.
*
* The database file path is configurable via the DATABASE_PATH env var.
* Defaults to data/protocol_droid.db relative to the project root.
*/
function db_path(): string {
$configured = getenv('DATABASE_PATH');
if ($configured) return $configured;
return __DIR__ . '/../data/protocol_droid.db';
}
function db(): PDO {
static $pdo = null;
if ($pdo === null) {
$path = db_path();
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
$dsn = "sqlite:{$path}";
try {
$pdo = new PDO($dsn, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
// Enable foreign key enforcement (off by default in SQLite)
$pdo->exec('PRAGMA foreign_keys = ON');
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database connection failed']);
exit;
}
}
return $pdo;
}
function db_init(): void {
$sql = file_get_contents(__DIR__ . '/../schema.sql');
if (!$sql) return;
db()->exec($sql);
}
function db_query(string $sql, array $params = []): PDOStatement {
$stmt = db()->prepare($sql);
$stmt->execute($params);
return $stmt;
}
function db_one(string $sql, array $params = []): ?array {
$stmt = db_query($sql, $params);
$row = $stmt->fetch();
return $row ?: null;
}
function db_all(string $sql, array $params = []): array {
$stmt = db_query($sql, $params);
return $stmt->fetchAll();
}
function db_insert(string $table, array $data): int {
$cols = implode(', ', array_map(fn($c) => "`$c`", array_keys($data)));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$sql = "INSERT INTO {$table} ({$cols}) VALUES ({$placeholders})";
db_query($sql, array_values($data));
return (int) db()->lastInsertId();
}
function db_update(string $table, array $data, string $where, array $whereParams): int {
$set = implode(', ', array_map(fn($c) => "`$c` = ?", array_keys($data)));
$sql = "UPDATE {$table} SET {$set} WHERE {$where}";
$stmt = db_query($sql, array_merge(array_values($data), $whereParams));
return $stmt->rowCount();
}
function slugify(string $text): string {
$slug = strtolower($text);
$slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
$slug = trim($slug, '-');
$slug = substr($slug, 0, 128);
return $slug ?: 'untitled';
}
function ensure_unique_slug(string $base): string {
$slug = $base;
$i = 1;
while (db_one("SELECT slug FROM protocols WHERE slug = ?", [$slug]) ||
db_one("SELECT slug FROM collections WHERE slug = ?", [$slug])) {
$slug = $base . '-' . $i++;
}
return $slug;
}