Commit 2acacd1a01444e011e0ffaa24ab8518fcd55b6e2

Authored by 谭苏航
1 parent 3785d15b

feat: add admin binding portal (wechat style)

  1 +<?php
  2 +require_once __DIR__ . '/../vendor/autoload.php';
  3 +
  4 +// 加载配置 (.env 手动加载逻辑同 start.php)
  5 +if (file_exists(__DIR__ . '/../.env')) {
  6 + $lines = file(__DIR__ . '/../.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  7 + foreach ($lines as $line) {
  8 + if (strpos(trim($line), '#') === 0)
  9 + continue;
  10 + list($name, $value) = explode('=', $line, 2);
  11 + $_ENV[trim($name)] = trim($value);
  12 + }
  13 +}
  14 +
  15 +// 简单的一致性检查 (实际生产环境应加上 Session 登录验证)
  16 +// 这里假设通过 Basic Auth 或内网访问
  17 +
  18 +// 连接数据库
  19 +try {
  20 + $dsn = "mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']};charset=utf8mb4";
  21 + $pdo = new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASS']);
  22 + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  23 +} catch (PDOException $e) {
  24 + die("Database connection failed: " . $e->getMessage());
  25 +}
  26 +
  27 +// 处理绑定表单提交
  28 +$message = '';
  29 +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'bind') {
  30 + $deviceId = trim($_POST['device_id']);
  31 + $phone = trim($_POST['phone']);
  32 + $isPrimary = isset($_POST['is_primary']) ? 1 : 0;
  33 +
  34 + if ($deviceId && $phone) {
  35 + // 1. 查找用户
  36 + $stmt = $pdo->prepare("SELECT id, nickname FROM users WHERE phone = ?");
  37 + $stmt->execute([$phone]);
  38 + $user = $stmt->fetch(PDO::FETCH_ASSOC);
  39 +
  40 + if ($user) {
  41 + // 2. 插入/更新绑定
  42 + // 先检查是否已存在
  43 + $check = $pdo->prepare("SELECT id FROM user_device_bindings WHERE user_id = ? AND device_id = ?");
  44 + $check->execute([$user['id'], $deviceId]);
  45 +
  46 + if (!$check->fetch()) {
  47 + $bind = $pdo->prepare("INSERT INTO user_device_bindings (user_id, device_id, is_primary, created_at) VALUES (?, ?, ?, NOW())");
  48 + $bind->execute([$user['id'], $deviceId, $isPrimary]);
  49 + $message = "<div style='color: green; margin-bottom: 20px;'> 成功将设备 <b>$deviceId</b> 绑定给用户 <b>{$user['nickname']}</b></div>";
  50 + } else {
  51 + $message = "<div style='color: orange; margin-bottom: 20px;'>⚠️ 该用户已经绑定过此设备,无需重复操作。</div>";
  52 + }
  53 + } else {
  54 + $message = "<div style='color: red; margin-bottom: 20px;'> 手机号 <b>$phone</b> 未找到。请确保用户已在小程序登录过。</div>";
  55 + }
  56 + }
  57 +}
  58 +
  59 +// 获取设备列表 (Mock Data + Binding Count)
  60 +// 实际项目应从 Redis 或 devices 表获取在线状态,这里先从 bindings 表反查活跃情况
  61 +// 为了简化,我们列出 distinct device_id from bindings,或者列出 bindings
  62 +$bindings = $pdo->query("
  63 + SELECT b.id, u.nickname, u.phone, b.device_id, b.is_primary, b.created_at
  64 + FROM user_device_bindings b
  65 + JOIN users u ON b.user_id = u.id
  66 + ORDER BY b.created_at DESC
  67 +")->fetchAll(PDO::FETCH_ASSOC);
  68 +
  69 +?>
  70 +<!DOCTYPE html>
  71 +<html lang="zh-CN">
  72 +
  73 +<head>
  74 + <meta charset="UTF-8">
  75 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
  76 + <title>Moltbot 管理后台</title>
  77 + <link rel="stylesheet" href="style.css">
  78 +</head>
  79 +
  80 +<body>
  81 +
  82 + <div class="header">
  83 + <div class="brand">Moltbot Admin</div>
  84 + <div>
  85 + <?php echo date('Y-m-d H:i'); ?>
  86 + </div>
  87 + </div>
  88 +
  89 + <div class="container">
  90 +
  91 + <?php echo $message; ?>
  92 +
  93 + <!-- 新增绑定卡片 -->
  94 + <div class="card">
  95 + <div class="title">新增绑定</div>
  96 + <form method="POST" action="">
  97 + <input type="hidden" name="action" value="bind">
  98 + <div style="display: flex; gap: 20px;">
  99 + <div class="form-group" style="flex: 1;">
  100 + <label class="form-label">设备 ID</label>
  101 + <input type="text" name="device_id" class="form-control" placeholder="例如: dev_test_001"
  102 + required>
  103 + </div>
  104 + <div class="form-group" style="flex: 1;">
  105 + <label class="form-label">用户手机号</label>
  106 + <input type="text" name="phone" class="form-control" placeholder="输入用户注册手机号" required>
  107 + </div>
  108 + </div>
  109 + <div class="form-group">
  110 + <label>
  111 + <input type="checkbox" name="is_primary" value="1" checked> 设为主设备 (默认)
  112 + </label>
  113 + </div>
  114 + <button type="submit" class="btn btn-primary">立即绑定</button>
  115 + </form>
  116 + </div>
  117 +
  118 + <!-- 绑定列表卡片 -->
  119 + <div class="card">
  120 + <div class="title">绑定记录 (
  121 + <?php echo count($bindings); ?>)
  122 + </div>
  123 + <table>
  124 + <thead>
  125 + <tr>
  126 + <th>用户</th>
  127 + <th>手机号</th>
  128 + <th>设备 ID</th>
  129 + <th>主设备</th>
  130 + <th>绑定时间</th>
  131 + <th>操作</th>
  132 + </tr>
  133 + </thead>
  134 + <tbody>
  135 + <?php foreach ($bindings as $row): ?>
  136 + <tr>
  137 + <td>
  138 + <?php echo htmlspecialchars($row['nickname']); ?>
  139 + </td>
  140 + <td>
  141 + <?php echo htmlspecialchars($row['phone']); ?>
  142 + </td>
  143 + <td>
  144 + <?php echo htmlspecialchars($row['device_id']); ?>
  145 + </td>
  146 + <td>
  147 + <?php if ($row['is_primary']): ?>
  148 + <span style="color: var(--primary-color);">✔</span>
  149 + <?php endif; ?>
  150 + </td>
  151 + <td>
  152 + <?php echo $row['created_at']; ?>
  153 + </td>
  154 + <td>
  155 + <a href="#" style="color: red; font-size: 12px; text-decoration: none;">解绑</a>
  156 + </td>
  157 + </tr>
  158 + <?php endforeach; ?>
  159 + <?php if (empty($bindings)): ?>
  160 + <tr>
  161 + <td colspan="6" style="text-align: center; color: #999;">暂无数据</td>
  162 + </tr>
  163 + <?php endif; ?>
  164 + </tbody>
  165 + </table>
  166 + </div>
  167 +
  168 + </div>
  169 +
  170 +</body>
  171 +
  172 +</html>
  1 +/* Admin Style - WeUI Inspired */
  2 +:root {
  3 + --primary-color: #07c160;
  4 + --primary-hover: #06ad56;
  5 + --bg-color: #f7f7f7;
  6 + --card-bg: #ffffff;
  7 + --text-main: #000000;
  8 + --text-secondary: #888888;
  9 + --border-color: #e5e5e5;
  10 +}
  11 +
  12 +body {
  13 + margin: 0;
  14 + padding: 0;
  15 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  16 + background-color: var(--bg-color);
  17 + color: var(--text-main);
  18 +}
  19 +
  20 +.header {
  21 + background-color: var(--card-bg);
  22 + padding: 15px 30px;
  23 + border-bottom: 1px solid var(--border-color);
  24 + display: flex;
  25 + justify-content: space-between;
  26 + align-items: center;
  27 +}
  28 +
  29 +.brand {
  30 + font-size: 18px;
  31 + font-weight: 600;
  32 + color: var(--text-main);
  33 +}
  34 +
  35 +.container {
  36 + max-width: 1000px;
  37 + margin: 30px auto;
  38 + padding: 0 20px;
  39 +}
  40 +
  41 +.card {
  42 + background-color: var(--card-bg);
  43 + border-radius: 8px;
  44 + box-shadow: 0 1px 3px rgba(0,0,0,0.05);
  45 + padding: 24px;
  46 + margin-bottom: 24px;
  47 +}
  48 +
  49 +.title {
  50 + font-size: 20px;
  51 + font-weight: 500;
  52 + margin-bottom: 24px;
  53 + border-left: 4px solid var(--primary-color);
  54 + padding-left: 12px;
  55 +}
  56 +
  57 +/* Table */
  58 +table {
  59 + width: 100%;
  60 + border-collapse: collapse;
  61 +}
  62 +
  63 +th {
  64 + text-align: left;
  65 + color: var(--text-secondary);
  66 + font-weight: normal;
  67 + font-size: 14px;
  68 + padding: 10px;
  69 + border-bottom: 1px solid var(--border-color);
  70 +}
  71 +
  72 +td {
  73 + padding: 12px 10px;
  74 + border-bottom: 1px solid var(--border-color);
  75 + font-size: 14px;
  76 +}
  77 +
  78 +.status-dot {
  79 + display: inline-block;
  80 + width: 8px;
  81 + height: 8px;
  82 + border-radius: 50%;
  83 + margin-right: 6px;
  84 +}
  85 +
  86 +.status-online { background-color: var(--primary-color); }
  87 +.status-offline { background-color: #ccc; }
  88 +
  89 +/* Buttons */
  90 +.btn {
  91 + display: inline-block;
  92 + padding: 8px 16px;
  93 + border-radius: 4px;
  94 + text-decoration: none;
  95 + font-size: 14px;
  96 + cursor: pointer;
  97 + border: none;
  98 + transition: background 0.2s;
  99 +}
  100 +
  101 +.btn-primary {
  102 + background-color: var(--primary-color);
  103 + color: white;
  104 +}
  105 +
  106 +.btn-primary:hover {
  107 + background-color: var(--primary-hover);
  108 +}
  109 +
  110 +.btn-sm {
  111 + padding: 4px 10px;
  112 + font-size: 12px;
  113 +}
  114 +
  115 +/* Forms */
  116 +.form-group {
  117 + margin-bottom: 16px;
  118 +}
  119 +
  120 +.form-label {
  121 + display: block;
  122 + margin-bottom: 8px;
  123 + color: var(--text-secondary);
  124 + font-size: 14px;
  125 +}
  126 +
  127 +.form-control {
  128 + width: 100%;
  129 + padding: 10px;
  130 + border: 1px solid var(--border-color);
  131 + border-radius: 4px;
  132 + font-size: 14px;
  133 + box-sizing: border-box;
  134 +}
  135 +
  136 +.form-control:focus {
  137 + outline: none;
  138 + border-color: var(--primary-color);
  139 +}
1 -<?php  
2 -require_once __DIR__ . '/vendor/autoload.php';  
3 -  
4 -// Load .env (simple loader since we don't have dotenv lib installed in the basic container,  
5 -// or relying on docker-compose env injection)  
6 -// Actually docker-compose injects env vars, so getenv() works.  
7 -  
8 -$host = getenv('DB_HOST');  
9 -$user = getenv('DB_USER');  
10 -$pass = getenv('DB_PASSWORD');  
11 -$name = getenv('DB_NAME');  
12 -$port = getenv('DB_PORT') ?: 3306;  
13 -  
14 -echo "Connecting to MySQL at $host...\n";  
15 -  
16 -try {  
17 - $dsn = "mysql:host=$host;port=$port;charset=utf8mb4";  
18 - $pdo = new PDO($dsn, $user, $pass, [  
19 - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,  
20 - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC  
21 - ]);  
22 -  
23 - echo "Connected successfully.\n";  
24 -  
25 - // Create Database if not exists  
26 - $pdo->exec("CREATE DATABASE IF NOT EXISTS `$name` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");  
27 - $pdo->exec("USE `$name`");  
28 - echo "Selected database '$name'.\n";  
29 -  
30 - $sql = <<<SQL  
31 --- Devices Table  
32 -CREATE TABLE IF NOT EXISTS `devices` (  
33 - `id` VARCHAR(64) NOT NULL COMMENT '设备ID (Device ID)',  
34 - `secret` VARCHAR(128) NOT NULL COMMENT '连接密钥 (Hash)',  
35 - `name` VARCHAR(50) DEFAULT NULL COMMENT '设备昵称',  
36 - `status` ENUM('online', 'offline') DEFAULT 'offline' COMMENT '在线状态',  
37 - `last_seen` TIMESTAMP NULL DEFAULT NULL COMMENT '最后心跳时间',  
38 - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  
39 - `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
40 - PRIMARY KEY (`id`)  
41 -) ENGINE=InnoDB COMMENT='设备管理表';  
42 -  
43 --- Users Table  
44 -CREATE TABLE IF NOT EXISTS `users` (  
45 - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  
46 - `phone` VARCHAR(20) NOT NULL COMMENT '手机号',  
47 - `wx_openid` VARCHAR(64) DEFAULT NULL COMMENT '微信 OpenID',  
48 - `wx_unionid` VARCHAR(64) DEFAULT NULL COMMENT '微信 UnionID',  
49 - `nickname` VARCHAR(50) DEFAULT NULL COMMENT '显示的昵称',  
50 - `avatar_url` VARCHAR(255) DEFAULT NULL,  
51 - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  
52 - `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
53 - PRIMARY KEY (`id`),  
54 - UNIQUE KEY `uk_phone` (`phone`),  
55 - KEY `idx_wx_openid` (`wx_openid`)  
56 -) ENGINE=InnoDB COMMENT='用户表';  
57 -  
58 --- Sessions Table (用户登录会话)  
59 -CREATE TABLE IF NOT EXISTS `sessions` (  
60 - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  
61 - `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',  
62 - `token` VARCHAR(128) NOT NULL COMMENT 'Session Token',  
63 - `device_info` JSON DEFAULT NULL COMMENT '登录设备信息',  
64 - `expires_at` TIMESTAMP NOT NULL COMMENT '过期时间',  
65 - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  
66 - PRIMARY KEY (`id`),  
67 - UNIQUE KEY `uk_token` (`token`),  
68 - KEY `idx_user` (`user_id`),  
69 - KEY `idx_expires` (`expires_at`)  
70 -) ENGINE=InnoDB COMMENT='用户会话表';  
71 -  
72 --- User Device Bindings Table (手机号-设备绑定关系)  
73 -CREATE TABLE IF NOT EXISTS `user_device_bindings` (  
74 - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  
75 - `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',  
76 - `device_id` VARCHAR(64) NOT NULL COMMENT '设备ID',  
77 - `is_primary` TINYINT(1) DEFAULT 0 COMMENT '是否主设备',  
78 - `bound_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '绑定时间',  
79 - PRIMARY KEY (`id`),  
80 - UNIQUE KEY `uk_user_device` (`user_id`, `device_id`),  
81 - KEY `idx_device` (`device_id`)  
82 -) ENGINE=InnoDB COMMENT='用户设备绑定表';  
83 -  
84 --- Device Logs Table  
85 -CREATE TABLE IF NOT EXISTS `device_logs` (  
86 - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  
87 - `device_id` VARCHAR(64) NOT NULL,  
88 - `level` VARCHAR(10) DEFAULT 'INFO',  
89 - `message` TEXT NOT NULL,  
90 - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  
91 - PRIMARY KEY (`id`),  
92 - KEY `idx_device_time` (`device_id`, `created_at`)  
93 -) ENGINE=InnoDB COMMENT='设备日志表';  
94 -SQL;  
95 -  
96 - $pdo->exec($sql);  
97 - echo "Tables created successfully.\n";  
98 -  
99 - // Insert sample device if not exists  
100 - $pdo->exec("INSERT IGNORE INTO `devices` (`id`, `secret`, `name`, `status`) VALUES ('dev_test_001', '123456', 'My First Moltbot', 'offline')");  
101 - echo "Sample data inserted.\n";  
102 -  
103 -} catch (PDOException $e) {  
104 - echo "Database Error: " . $e->getMessage() . "\n";  
105 - exit(1);  
106 -} 1 +<?php
  2 +require_once __DIR__ . '/vendor/autoload.php';
  3 +
  4 +// Load .env (simple loader since we don't have dotenv lib installed in the basic container,
  5 +// or relying on docker-compose env injection)
  6 +// Actually docker-compose injects env vars, so getenv() works.
  7 +
  8 +$host = getenv('DB_HOST');
  9 +$user = getenv('DB_USER');
  10 +$pass = getenv('DB_PASSWORD');
  11 +$name = getenv('DB_NAME');
  12 +$port = getenv('DB_PORT') ?: 3306;
  13 +
  14 +echo "Connecting to MySQL at $host...\n";
  15 +
  16 +try {
  17 + $dsn = "mysql:host=$host;port=$port;charset=utf8mb4";
  18 + $pdo = new PDO($dsn, $user, $pass, [
  19 + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  20 + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
  21 + ]);
  22 +
  23 + echo "Connected successfully.\n";
  24 +
  25 + // Create Database if not exists
  26 + $pdo->exec("CREATE DATABASE IF NOT EXISTS `$name` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
  27 + $pdo->exec("USE `$name`");
  28 + echo "Selected database '$name'.\n";
  29 +
  30 + $sql = <<<SQL
  31 +-- Devices Table
  32 +CREATE TABLE IF NOT EXISTS `devices` (
  33 + `id` VARCHAR(64) NOT NULL COMMENT '设备ID (Device ID)',
  34 + `secret` VARCHAR(128) NOT NULL COMMENT '连接密钥 (Hash)',
  35 + `name` VARCHAR(50) DEFAULT NULL COMMENT '设备昵称',
  36 + `status` ENUM('online', 'offline') DEFAULT 'offline' COMMENT '在线状态',
  37 + `last_seen` TIMESTAMP NULL DEFAULT NULL COMMENT '最后心跳时间',
  38 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  39 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  40 + PRIMARY KEY (`id`)
  41 +) ENGINE=InnoDB COMMENT='设备管理表';
  42 +
  43 +-- Users Table
  44 +CREATE TABLE IF NOT EXISTS `users` (
  45 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  46 + `phone` VARCHAR(20) NOT NULL COMMENT '手机号',
  47 + `wx_openid` VARCHAR(64) DEFAULT NULL COMMENT '微信 OpenID',
  48 + `wx_unionid` VARCHAR(64) DEFAULT NULL COMMENT '微信 UnionID',
  49 + `nickname` VARCHAR(50) DEFAULT NULL COMMENT '显示的昵称',
  50 + `avatar_url` VARCHAR(255) DEFAULT NULL,
  51 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  52 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  53 + PRIMARY KEY (`id`),
  54 + UNIQUE KEY `uk_phone` (`phone`),
  55 + KEY `idx_wx_openid` (`wx_openid`)
  56 +) ENGINE=InnoDB COMMENT='用户表';
  57 +
  58 +-- Sessions Table (用户登录会话)
  59 +CREATE TABLE IF NOT EXISTS `sessions` (
  60 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  61 + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
  62 + `token` VARCHAR(128) NOT NULL COMMENT 'Session Token',
  63 + `device_info` JSON DEFAULT NULL COMMENT '登录设备信息',
  64 + `expires_at` TIMESTAMP NOT NULL COMMENT '过期时间',
  65 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  66 + PRIMARY KEY (`id`),
  67 + UNIQUE KEY `uk_token` (`token`),
  68 + KEY `idx_user` (`user_id`),
  69 + KEY `idx_expires` (`expires_at`)
  70 +) ENGINE=InnoDB COMMENT='用户会话表';
  71 +
  72 +-- User Device Bindings Table (手机号-设备绑定关系)
  73 +CREATE TABLE IF NOT EXISTS `user_device_bindings` (
  74 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  75 + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
  76 + `device_id` VARCHAR(64) NOT NULL COMMENT '设备ID',
  77 + `is_primary` TINYINT(1) DEFAULT 0 COMMENT '是否主设备',
  78 + `bound_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '绑定时间',
  79 + PRIMARY KEY (`id`),
  80 + UNIQUE KEY `uk_user_device` (`user_id`, `device_id`),
  81 + KEY `idx_device` (`device_id`)
  82 +) ENGINE=InnoDB COMMENT='用户设备绑定表';
  83 +
  84 +-- Device Logs Table
  85 +CREATE TABLE IF NOT EXISTS `device_logs` (
  86 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  87 + `device_id` VARCHAR(64) NOT NULL,
  88 + `level` VARCHAR(10) DEFAULT 'INFO',
  89 + `message` TEXT NOT NULL,
  90 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  91 + PRIMARY KEY (`id`),
  92 + KEY `idx_device_time` (`device_id`, `created_at`)
  93 +) ENGINE=InnoDB COMMENT='设备日志表';
  94 +SQL;
  95 +
  96 + $pdo->exec($sql);
  97 + echo "Tables created successfully.\n";
  98 +
  99 + // Insert sample device if not exists
  100 + $pdo->exec("INSERT IGNORE INTO `devices` (`id`, `secret`, `name`, `status`) VALUES ('dev_test_001', '123456', 'My First Moltbot', 'offline')");
  101 + echo "Sample data inserted.\n";
  102 +
  103 +} catch (PDOException $e) {
  104 + echo "Database Error: " . $e->getMessage() . "\n";
  105 + exit(1);
  106 +}
Please register or login to post a comment