Initial version upload

This commit is contained in:
Michael Staake
2025-11-03 11:04:46 -08:00
parent 8a3c48b98a
commit 0083f20e8c
26 changed files with 2281 additions and 0 deletions

41
core/Controller.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
class Controller {
protected function view(string $viewName, array $data = []): void {
extract($data);
$viewPath = BASE_PATH . '/views/' . $viewName . '.php';
if (!file_exists($viewPath)) {
die("View not found: $viewName");
}
require_once $viewPath;
}
protected function redirect(string $path): void {
// Add base URL if path doesn't already include it
$url = BASE_URL . $path;
header('Location: ' . $url);
exit;
}
protected function json(mixed $data, int $statusCode = 200): void {
http_response_code($statusCode);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
protected function requireAuth(): void {
if (!isset($_SESSION['user_id'])) {
$this->redirect('/login');
}
}
protected function requireSetup(): void {
if (!Database::isInitialized()) {
$this->redirect('/setup');
}
}
}

130
core/Database.php Normal file
View File

@@ -0,0 +1,130 @@
<?php
require_once BASE_PATH . '/config.php';
class Database {
private static ?PDO $instance = null;
public static function getInstance(): PDO {
if (self::$instance === null) {
try {
self::$instance = new PDO('sqlite:' . DB_PATH);
self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
self::$instance->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die('Database connection failed: ' . $e->getMessage());
}
}
return self::$instance;
}
public static function isInitialized(): bool {
if (!file_exists(DB_PATH)) {
return false;
}
try {
$db = self::getInstance();
$result = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='users'");
return $result->fetch() !== false;
} catch (PDOException $e) {
return false;
}
}
public static function initialize(string $password): bool {
try {
$db = self::getInstance();
// Create users table
$db->exec("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
password_hash TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
// Create vehicles table
$db->exec("
CREATE TABLE IF NOT EXISTS vehicles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
year TEXT,
make TEXT,
model TEXT,
color TEXT,
license_plate TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
// Create maintenance_items table
$db->exec("
CREATE TABLE IF NOT EXISTS maintenance_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vehicle_id INTEGER NOT NULL,
name TEXT NOT NULL,
date DATE NOT NULL,
mileage INTEGER NOT NULL,
description TEXT,
cost REAL,
parts_list TEXT,
performed_by TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (vehicle_id) REFERENCES vehicles(id) ON DELETE CASCADE
)
");
// Create quick_tasks table (predefined maintenance items)
$db->exec("
CREATE TABLE IF NOT EXISTS quick_tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
// Insert default password
$stmt = $db->prepare("INSERT INTO users (password_hash) VALUES (?)");
$stmt->execute([password_hash($password, PASSWORD_DEFAULT)]);
// Insert default quick tasks
$defaultTasks = [
'Oil Change - Oil and Filter',
'Oil Change - Oil Only',
'Tire Rotation',
'Air Filter Replacement',
'Battery Replacement',
'Spark Plug Replacement',
'Wiper Blade Replacement - Front',
'Wiper Blade Replacement - Rear',
'Wiper Blade Replacement - Front and Rear',
'Cabin Air Filter Replacement',
'Wheel Alignment',
'Tire Replacement',
'Brake Fluid Change',
'Power Steering Fluid Change',
'Fuel Filter Replacement',
'Serpentine Belt Replacement',
'Timing Belt Replacement',
'Inspection/Emissions Test',
'Registration Renewed',
];
$stmt = $db->prepare("INSERT INTO quick_tasks (name) VALUES (?)");
foreach ($defaultTasks as $task) {
$stmt->execute([$task]);
}
return true;
} catch (PDOException $e) {
error_log('Database initialization failed: ' . $e->getMessage());
return false;
}
}
}

74
core/Router.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
class Router {
private array $routes = [];
public function get(string $path, string $handler): void {
$this->addRoute('GET', $path, $handler);
}
public function post(string $path, string $handler): void {
$this->addRoute('POST', $path, $handler);
}
private function addRoute(string $method, string $path, string $handler): void {
$this->routes[] = [
'method' => $method,
'path' => $path,
'handler' => $handler
];
}
public function dispatch(string $uri, string $method): void {
// Remove query string
$uri = parse_url($uri, PHP_URL_PATH);
// Remove base directory from URI if running in subdirectory
$scriptName = dirname($_SERVER['SCRIPT_NAME']);
if ($scriptName !== '/' && strpos($uri, $scriptName) === 0) {
$uri = substr($uri, strlen($scriptName));
}
// Ensure URI starts with /
if (empty($uri) || $uri[0] !== '/') {
$uri = '/' . $uri;
}
// Remove trailing slash except for root
if ($uri !== '/' && str_ends_with($uri, '/')) {
$uri = rtrim($uri, '/');
}
foreach ($this->routes as $route) {
if ($route['method'] !== $method) {
continue;
}
$pattern = $this->convertToRegex($route['path']);
if (preg_match($pattern, $uri, $matches)) {
array_shift($matches); // Remove full match
$this->callHandler($route['handler'], $matches);
return;
}
}
// 404 Not Found
http_response_code(404);
echo '404 - Page Not Found<br>';
echo 'Requested URI: ' . htmlspecialchars($uri) . '<br>';
echo 'Request Method: ' . htmlspecialchars($method);
}
private function convertToRegex(string $path): string {
$path = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '([a-zA-Z0-9_-]+)', $path);
return '#^' . $path . '$#';
}
private function callHandler(string $handler, array $params): void {
[$controller, $method] = explode('@', $handler);
$controllerInstance = new $controller();
call_user_func_array([$controllerInstance, $method], $params);
}
}