feat: Initialize License Tracker Pro v1.2 project

Adds initial project structure, dependencies, and basic configuration for the License Tracker Pro v1.2 application. This includes setting up Vite for the build process, defining core types, and incorporating essential UI components like icons and a timeline. The project is now ready for development.
This commit is contained in:
Philip
2026-01-26 18:55:43 -08:00
parent d4895c6d81
commit e045354892
56 changed files with 3410 additions and 8 deletions
+379
View File
@@ -0,0 +1,379 @@
<?php
/**
* LICENSE TRACKER PRO - COMPLETE UNIFIED API BACKEND
*/
// 1. SYSTEM CONFIGURATION
error_reporting(E_ALL);
ini_set('display_errors', 0);
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
header('Content-Type: application/json');
// 2. DATABASE CONNECTION
$db_host = getenv('DB_HOST') ?: 'db';
$db_name = getenv('DB_NAME') ?: 'license_tracker';
$db_user = getenv('DB_USER') ?: 'root';
$db_pass = getenv('DB_PASS') ?: 'root_password';
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=utf8mb4", $db_user, $db_pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_TIMEOUT => 10
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database connection failed: ' . $e->getMessage()]);
exit;
}
// 3. SCHEMA INITIALIZATION & MIGRATIONS
try {
$pdo->exec("CREATE TABLE IF NOT EXISTS settings (id INT PRIMARY KEY DEFAULT 1, config LONGTEXT NOT NULL) ENGINE=InnoDB;");
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, email VARCHAR(100), role ENUM('admin', 'editor') DEFAULT 'editor', auth_source VARCHAR(20) DEFAULT 'local', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB;");
$pdo->exec("CREATE TABLE IF NOT EXISTS licenses (id VARCHAR(50) PRIMARY KEY, licenseName VARCHAR(255) NOT NULL, companyName VARCHAR(255), responsiblePerson VARCHAR(255), purchaseDate DATE, endDate DATE, purchasePrice DECIMAL(15, 2), vendorName VARCHAR(255), vendorContact LONGTEXT, notificationGroupId INT NULL, comments TEXT, tags TEXT, isActive BOOLEAN DEFAULT 1, image LONGTEXT, alert_level INT DEFAULT 0, last_alert_at DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB;");
$pdo->exec("CREATE TABLE IF NOT EXISTS renewals (id VARCHAR(50) PRIMARY KEY, license_id VARCHAR(50), renewalDate DATE, endDate DATE, renewalPrice DECIMAL(15, 2), notes TEXT, FOREIGN KEY (license_id) REFERENCES licenses(id) ON DELETE CASCADE) ENGINE=InnoDB;");
$pdo->exec("CREATE TABLE IF NOT EXISTS files (id INT AUTO_INCREMENT PRIMARY KEY, license_id VARCHAR(50), fileName VARCHAR(255), mimeType VARCHAR(100), fileSize INT, fileData LONGBLOB, FOREIGN KEY (license_id) REFERENCES licenses(id) ON DELETE CASCADE) ENGINE=InnoDB;");
$pdo->exec("CREATE TABLE IF NOT EXISTS notification_groups (id INT AUTO_INCREMENT PRIMARY KEY, groupName VARCHAR(100) NOT NULL, emails TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB;");
$pdo->exec("CREATE TABLE IF NOT EXISTS contacts (id INT AUTO_INCREMENT PRIMARY KEY, vendorName VARCHAR(255) UNIQUE NOT NULL, contactName VARCHAR(255), contactEmail VARCHAR(255), contactPhone VARCHAR(50)) ENGINE=InnoDB;");
$pdo->exec("CREATE TABLE IF NOT EXISTS audit_logs (id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, username VARCHAR(50), action_type VARCHAR(50), entity_type VARCHAR(50), entity_id VARCHAR(50), entity_name VARCHAR(255), details TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB;");
// Dynamic Column Checks
$cols = $pdo->query("DESCRIBE licenses")->fetchAll(PDO::FETCH_COLUMN);
if (!in_array('image', $cols)) $pdo->exec("ALTER TABLE licenses ADD COLUMN image LONGTEXT AFTER isActive");
if (!in_array('alert_level', $cols)) $pdo->exec("ALTER TABLE licenses ADD COLUMN alert_level INT DEFAULT 0 AFTER image");
if (!in_array('last_alert_at', $cols)) $pdo->exec("ALTER TABLE licenses ADD COLUMN last_alert_at DATETIME NULL AFTER alert_level");
if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) {
$hash = password_hash('admin123', PASSWORD_DEFAULT);
$pdo->prepare("INSERT INTO users (username, password, role, email) VALUES (?, ?, 'admin', 'admin@example.com')")->execute(['admin', $hash]);
}
} catch (Exception $e) {}
// 4. HELPERS
function respond($data, $code = 200) {
http_response_code($code);
echo json_encode($data);
exit;
}
function isAuth() { if (!isset($_SESSION['user'])) respond(['error' => 'Authentication Required'], 401); }
function isAdmin() { isAuth(); if (($_SESSION['user']['role'] ?? '') !== 'admin') respond(['error' => 'Admin access required'], 403); }
function logAudit($pdo, $type, $entity, $id, $name, $details) {
try {
$stmt = $pdo->prepare("INSERT INTO audit_logs (user_id, username, action_type, entity_type, entity_id, entity_name, details) VALUES (?,?,?,?,?,?,?)");
$stmt->execute([$_SESSION['user']['id'] ?? 0, $_SESSION['user']['username'] ?? 'system', $type, $entity, (string)$id, $name, $details]);
} catch (Exception $e) {}
}
/**
* Enhanced SMTP Socket Mailer
*/
function sendEmail($settings, $to, $subject, $body) {
if (empty($settings['smtp_host'])) return "SMTP Host is missing in configuration.";
$host = $settings['smtp_host'];
$port = $settings['smtp_port'];
$user = $settings['smtp_user'];
$pass = $settings['smtp_pass'];
$from = $settings['smtp_from'] ?: 'tracker@system.local';
$address = ($port == 465) ? "ssl://$host" : $host;
$socket = @fsockopen($address, $port, $errno, $errstr, 15);
if (!$socket) return "Connection Failed to $address:$port. Error: $errstr ($errno)";
$getResponse = function($socket) {
$res = "";
while($str = fgets($socket, 515)) {
$res .= $str;
if(substr($str, 3, 1) == " ") break;
}
return $res;
};
$sendCommand = function($socket, $cmd) use ($getResponse) {
fputs($socket, $cmd . "\r\n");
return $getResponse($socket);
};
$getResponse($socket);
$sendCommand($socket, "EHLO " . $_SERVER['HTTP_HOST']);
if (!empty($user)) {
$sendCommand($socket, "AUTH LOGIN");
$sendCommand($socket, base64_encode($user));
$sendCommand($socket, base64_encode($pass));
}
$sendCommand($socket, "MAIL FROM: <$from>");
$recipients = array_map('trim', explode(',', $to));
foreach ($recipients as $recipient) { $sendCommand($socket, "RCPT TO: <$recipient>"); }
$sendCommand($socket, "DATA");
$header = "To: $to\r\nFrom: License Tracker <$from>\r\nSubject: $subject\r\n";
$header .= "MIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\nDate: " . date("r") . "\r\n\r\n";
$sendCommand($socket, $header . $body . "\r\n.");
$sendCommand($socket, "QUIT");
fclose($socket);
return true;
}
$default_settings = [
'fiscal_start_month' => 4, 'alert_days' => 45, 'allow_local_login' => true,
'smtp_from' => '', 'smtp_host' => '', 'smtp_port' => '465', 'smtp_user' => '', 'smtp_pass' => ''
];
// 5. API ROUTING
$action = $_GET['action'] ?? '';
$input = json_decode(file_get_contents('php://input'), true) ?? [];
switch ($action) {
case 'check_session': respond(['authenticated' => isset($_SESSION['user']), 'user' => $_SESSION['user'] ?? null]); break;
case 'login':
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$input['username'] ?? '']);
$u = $stmt->fetch();
if ($u && password_verify($input['password'] ?? '', $u['password'])) {
unset($u['password']);
$_SESSION['user'] = $u;
logAudit($pdo, 'LOGIN', 'user', $u['id'], $u['username'], 'Logged in to system');
respond(['success' => true, 'user' => $u]);
}
respond(['error' => 'Invalid credentials'], 401);
break;
case 'logout':
logAudit($pdo, 'LOGOUT', 'user', $_SESSION['user']['id'] ?? 0, $_SESSION['user']['username'] ?? 'system', 'Logged out');
session_destroy();
respond(['success' => true]);
break;
case 'list':
$lics = $pdo->query("SELECT * FROM licenses ORDER BY created_at DESC")->fetchAll();
foreach ($lics as &$l) {
$l['vendorContact'] = json_decode($l['vendorContact'] ?? '{}', true);
$l['tags'] = $l['tags'] ? explode(',', $l['tags']) : [];
$l['isActive'] = (bool)$l['isActive'];
$stR = $pdo->prepare("SELECT * FROM renewals WHERE license_id = ? ORDER BY renewalDate DESC");
$stR->execute([$l['id']]);
$l['renewals'] = $stR->fetchAll();
$stF = $pdo->prepare("SELECT id, fileName, mimeType, fileSize FROM files WHERE license_id = ?");
$stF->execute([$l['id']]);
$l['files'] = $stF->fetchAll();
}
respond($lics);
break;
case 'save':
isAuth();
$isUpdate = isset($input['id']);
$id = $input['id'] ?? uniqid('lic_');
$pdo->beginTransaction();
try {
$alertLevel = 0;
if ($isUpdate) {
$stA = $pdo->prepare("SELECT alert_level FROM licenses WHERE id = ?");
$stA->execute([$id]);
$alertLevel = $stA->fetchColumn() ?: 0;
}
// 1. Upsert License
$pdo->prepare("DELETE FROM licenses WHERE id = ?")->execute([$id]);
$stmt = $pdo->prepare("INSERT INTO licenses (id, licenseName, companyName, responsiblePerson, purchaseDate, endDate, purchasePrice, vendorName, vendorContact, notificationGroupId, comments, tags, isActive, image, alert_level) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
$stmt->execute([
$id, $input['licenseName'], $input['companyName'] ?? '', $input['responsiblePerson'] ?? '',
$input['purchaseDate'], $input['endDate'] ?: null, (float)($input['purchasePrice'] ?? 0),
$input['vendorName'] ?? '', json_encode($input['vendorContact'] ?? []), $input['notificationGroupId'] ?? null,
$input['comments'] ?? '', is_array($input['tags']) ? implode(',', $input['tags']) : '', (int)($input['isActive'] ?? 1),
$input['image'] ?? null, $alertLevel
]);
// 2. Sync Renewals (History)
$pdo->prepare("DELETE FROM renewals WHERE license_id = ?")->execute([$id]);
if (!empty($input['renewals'])) {
$stR = $pdo->prepare("INSERT INTO renewals (id, license_id, renewalDate, endDate, renewalPrice, notes) VALUES (?,?,?,?,?,?)");
foreach ($input['renewals'] as $r) {
$stR->execute([$r['id'] ?: uniqid('r_'), $id, $r['renewalDate'], $r['endDate'] ?? null, (float)$r['renewalPrice'], $r['notes'] ?? '']);
}
}
// 3. Process File Attachments
if (!empty($input['newFiles'])) {
$stF = $pdo->prepare("INSERT INTO files (license_id, fileName, mimeType, fileSize, fileData) VALUES (?,?,?,?,?)");
foreach ($input['newFiles'] as $f) {
$parts = explode(',', $f['data']);
$data = count($parts) > 1 ? $parts[1] : $parts[0];
$decodedData = base64_decode($data);
$stF->execute([$id, $f['name'], $f['type'], strlen($decodedData), $decodedData]);
}
}
// 4. Update Global Contacts List
if (!empty($input['vendorName'])) {
$stC = $pdo->prepare("INSERT INTO contacts (vendorName, contactName, contactEmail, contactPhone) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE contactName=VALUES(contactName), contactEmail=VALUES(contactEmail), contactPhone=VALUES(contactPhone)");
$stC->execute([
$input['vendorName'],
$input['vendorContact']['name'] ?? '',
$input['vendorContact']['email'] ?? '',
$input['vendorContact']['phone'] ?? ''
]);
}
$pdo->commit();
logAudit($pdo, $isUpdate ? 'UPDATE' : 'CREATE', 'license', $id, $input['licenseName'], $isUpdate ? 'Updated license and associated records' : 'Created new license entry');
respond(['success' => true]);
} catch (Exception $e) { if ($pdo->inTransaction()) $pdo->rollBack(); respond(['error' => $e->getMessage()], 500); }
break;
case 'delete': isAuth(); $pdo->prepare("DELETE FROM licenses WHERE id = ?")->execute([$_GET['id']]); respond(['success' => true]); break;
case 'users_list': isAdmin(); respond($pdo->query("SELECT id, username, email, role, auth_source, created_at FROM users")->fetchAll()); break;
case 'users_create':
isAdmin();
$hash = password_hash($input['password'], PASSWORD_DEFAULT);
$pdo->prepare("INSERT INTO users (username, password, email, role, auth_source) VALUES (?,?,?,?,?)")->execute([$input['username'], $hash, $input['email'], $input['role'], $input['auth_source']]);
logAudit($pdo, 'CREATE', 'user', $pdo->lastInsertId(), $input['username'], 'Created user account');
respond(['success' => true]);
break;
case 'users_update':
isAdmin();
$id = $_GET['id'];
if (!empty($input['password'])) {
$hash = password_hash($input['password'], PASSWORD_DEFAULT);
$pdo->prepare("UPDATE users SET username=?, password=?, email=?, role=?, auth_source=? WHERE id=?")->execute([$input['username'], $hash, $input['email'], $input['role'], $input['auth_source'], $id]);
logAudit($pdo, 'UPDATE', 'user', $id, $input['username'], 'Updated user with password reset');
} else {
$pdo->prepare("UPDATE users SET username=?, email=?, role=?, auth_source=? WHERE id=?")->execute([$input['username'], $input['email'], $input['role'], $input['auth_source'], $id]);
logAudit($pdo, 'UPDATE', 'user', $id, $input['username'], 'Updated user profile');
}
respond(['success' => true]);
break;
case 'users_delete': isAdmin(); if ($_GET['id'] != 1) $pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$_GET['id']]); respond(['success' => true]); break;
case 'logs_list': isAdmin(); respond($pdo->query("SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 200")->fetchAll()); break;
case 'settings_get':
$s = $pdo->query("SELECT config FROM settings WHERE id = 1")->fetch();
respond($s ? array_merge($default_settings, json_decode($s['config'], true)) : $default_settings);
break;
case 'settings_save':
isAdmin();
$pdo->prepare("INSERT INTO settings (id, config) VALUES (1, ?) ON DUPLICATE KEY UPDATE config = VALUES(config)")->execute([json_encode($input)]);
logAudit($pdo, 'UPDATE', 'settings', '1', 'Configuration', 'Changed system settings');
respond(['success' => true]);
break;
case 'groups_list': respond($pdo->query("SELECT * FROM notification_groups ORDER BY groupName")->fetchAll()); break;
case 'groups_save': isAdmin(); $pdo->prepare("INSERT INTO notification_groups (groupName, emails) VALUES (?,?)")->execute([$input['groupName'], $input['emails']]); respond(['success' => true]); break;
case 'groups_delete': isAdmin(); $pdo->prepare("DELETE FROM notification_groups WHERE id = ?")->execute([$_GET['id']]); respond(['success' => true]); break;
case 'contacts_list': respond($pdo->query("SELECT * FROM contacts ORDER BY vendorName")->fetchAll()); break;
case 'contacts_save': isAuth(); $pdo->prepare("INSERT INTO contacts (vendorName, contactName, contactEmail, contactPhone) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE contactName=VALUES(contactName), contactEmail=VALUES(contactEmail), contactPhone=VALUES(contactPhone)")->execute([$input['vendorName'], $input['contactName'], $input['contactEmail'], $input['contactPhone']]); respond(['success' => true]); break;
case 'contacts_delete': isAdmin(); $pdo->prepare("DELETE FROM contacts WHERE id = ?")->execute([$_GET['id']]); respond(['success' => true]); break;
case 'run_notifications':
$s = $pdo->query("SELECT config FROM settings WHERE id = 1")->fetch();
$settings = $s ? array_merge($default_settings, json_decode($s['config'], true)) : $default_settings;
$threshold = (int)$settings['alert_days'];
$today = new DateTime();
$lics = $pdo->query("SELECT l.*, g.emails FROM licenses l LEFT JOIN notification_groups g ON l.notificationGroupId = g.id WHERE l.isActive = 1 AND l.endDate IS NOT NULL AND l.notificationGroupId IS NOT NULL")->fetchAll();
$sent = 0;
foreach ($lics as $l) {
$expiry = new DateTime($l['endDate']);
$diff = $today->diff($expiry);
$daysLeft = $diff->invert ? -$diff->days : $diff->days;
$currentLvl = (int)$l['alert_level'];
$targetLvl = 0;
if ($daysLeft <= 0) $targetLvl = 3;
else if ($daysLeft <= ($threshold / 2)) $targetLvl = 2;
else if ($daysLeft <= $threshold) $targetLvl = 1;
if ($targetLvl > $currentLvl) {
$labels = [1 => "Initial Alert", 2 => "Half-way Follow-up", 3 => "Day of Expiration!"];
$subject = "[{$labels[$targetLvl]}] License Expiry: {$l['licenseName']}";
$body = "<h2>Expiration Alert</h2><p><b>License:</b> {$l['licenseName']}<br><b>Expiry:</b> {$l['endDate']}<br><b>Stage:</b> {$labels[$targetLvl]}</p>";
if (sendEmail($settings, $l['emails'], $subject, $body) === true) {
$pdo->prepare("UPDATE licenses SET alert_level = ?, last_alert_at = NOW() WHERE id = ?")->execute([$targetLvl, $l['id']]);
logAudit($pdo, 'ACTION', 'system', $l['id'], $l['licenseName'], "Sent level $targetLvl email to {$l['emails']}");
$sent++;
}
}
}
respond(['success' => true, 'count' => $sent]);
break;
case 'test_email':
isAdmin();
$s = $pdo->query("SELECT config FROM settings WHERE id = 1")->fetch();
$settings = $s ? array_merge($default_settings, json_decode($s['config'], true)) : $default_settings;
$res = sendEmail($settings, $input['email'], "System Test", "<h1>SMTP Test Successful</h1>");
if ($res === true) respond(['success' => true]); else respond(['error' => $res], 500);
break;
case 'export':
isAdmin();
$data = [
'licenses' => $pdo->query("SELECT * FROM licenses")->fetchAll(),
'renewals' => $pdo->query("SELECT * FROM renewals")->fetchAll(),
'contacts' => $pdo->query("SELECT * FROM contacts")->fetchAll(),
'groups' => $pdo->query("SELECT * FROM notification_groups")->fetchAll(),
'files' => $pdo->query("SELECT id, license_id, fileName, mimeType, fileSize, TO_BASE64(fileData) as fileDataBase64 FROM files")->fetchAll()
];
respond($data);
break;
case 'import':
isAdmin();
try {
$pdo->beginTransaction();
$pdo->exec("SET FOREIGN_KEY_CHECKS = 0;");
$pdo->exec("DELETE FROM files; DELETE FROM renewals; DELETE FROM licenses; DELETE FROM contacts; DELETE FROM notification_groups;");
if (!empty($input['licenses'])) {
$st = $pdo->prepare("INSERT INTO licenses (id, licenseName, companyName, responsiblePerson, purchaseDate, endDate, purchasePrice, vendorName, vendorContact, notificationGroupId, comments, tags, isActive, image, alert_level) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
foreach ($input['licenses'] as $l) $st->execute([$l['id'], $l['licenseName'], $l['companyName']??'', $l['responsiblePerson']??'', $l['purchaseDate'], $l['endDate'], $l['purchasePrice'], $l['vendorName']??'', is_array($l['vendorContact'])?json_encode($l['vendorContact']):'{}', $l['notificationGroupId']??null, $l['comments']??'', is_array($l['tags'])?implode(',',$l['tags']):'', (int)($l['isActive']??1), $l['image']??null, $l['alert_level']??0]);
}
if (!empty($input['renewals'])) {
$st = $pdo->prepare("INSERT INTO renewals (id, license_id, renewalDate, endDate, renewalPrice, notes) VALUES (?,?,?,?,?,?)");
foreach ($input['renewals'] as $r) $st->execute([$r['id'], $r['license_id'], $r['renewalDate'], $r['endDate']??null, (float)$r['renewalPrice'], $r['notes']??'']);
}
if (!empty($input['files'])) {
$st = $pdo->prepare("INSERT INTO files (id, license_id, fileName, mimeType, fileSize, fileData) VALUES (?,?,?,?,?,?)");
foreach ($input['files'] as $f) { $data = base64_decode($f['fileDataBase64']??''); $st->execute([$f['id'], $f['license_id'], $f['fileName'], $f['mimeType'], $f['fileSize'], $data]); }
}
if (!empty($input['contacts'])) {
$st = $pdo->prepare("INSERT INTO contacts (id, vendorName, contactName, contactEmail, contactPhone) VALUES (?,?,?,?,?)");
foreach ($input['contacts'] as $c) $st->execute([$c['id'], $c['vendorName'], $c['contactName']??'', $c['contactEmail']??'', $c['contactPhone']??'']);
}
if (!empty($input['groups'])) {
$st = $pdo->prepare("INSERT INTO notification_groups (id, groupName, emails) VALUES (?,?,?)");
foreach ($input['groups'] as $g) $st->execute([$g['id'], $g['groupName'], $g['emails']]);
}
$pdo->exec("SET FOREIGN_KEY_CHECKS = 1;");
$pdo->commit();
logAudit($pdo, 'RESTORE', 'system', '0', 'Database', 'Imported full backup file');
respond(['success' => true]);
} catch (Exception $e) { if ($pdo->inTransaction()) $pdo->rollBack(); respond(['error' => $e->getMessage()], 500); }
break;
case 'file_delete': isAuth(); $pdo->prepare("DELETE FROM files WHERE id = ?")->execute([$_GET['id']]); respond(['success' => true]); break;
case 'file_download':
$st = $pdo->prepare("SELECT fileName, mimeType, fileData FROM files WHERE id = ?"); $st->execute([$_GET['id']]); $f = $st->fetch();
if ($f) { header("Content-Type: ".$f['mimeType']); header("Content-Disposition: attachment; filename=\"".$f['fileName']."\""); echo $f['fileData']; exit; }
respond(['error' => 'Not found'], 404);
break;
default: respond(['error' => 'Action not found: ' . $action], 404);
}