version 1.0

This commit is contained in:
2025-11-21 17:10:05 +08:00
parent b904e92da9
commit 2f9fba663a
655 changed files with 2417 additions and 374 deletions
+440
View File
@@ -0,0 +1,440 @@
<?php
// install.php - установщик приложения
// Проверяем, не установлено ли приложение уже
if (file_exists('config/config.php')) {
die('Приложение уже установлено. Для переустановки удалите файл config/config.php');
}
// SQL для создания таблиц
$database_sql = <<<SQL
SET FOREIGN_KEY_CHECKS=0;
-- Удаляем существующие таблицы в правильном порядке (сначала дочерние, потом родительские)
DROP TABLE IF EXISTS `user_sessions`;
DROP TABLE IF EXISTS `chapters`;
DROP TABLE IF EXISTS `books`;
DROP TABLE IF EXISTS `series`;
DROP TABLE IF EXISTS `users`;
-- Таблица пользователей
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`display_name` varchar(255) DEFAULT NULL,
`password_hash` varchar(255) NOT NULL,
`email` varchar(255) DEFAULT NULL,
`avatar` varchar(500) DEFAULT NULL,
`bio` text DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`last_login` timestamp NULL DEFAULT NULL,
`is_active` tinyint(1) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- Таблица серий
CREATE TABLE `series` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`description` text DEFAULT NULL,
`user_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `idx_series_user_id` (`user_id`),
CONSTRAINT `series_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- Таблица книг
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`description` text DEFAULT NULL,
`genre` varchar(100) DEFAULT NULL,
`cover_image` varchar(500) DEFAULT NULL,
`user_id` int(11) NOT NULL,
`series_id` int(11) DEFAULT NULL,
`sort_order_in_series` int(11) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`share_token` varchar(32) DEFAULT NULL,
`published` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `share_token` (`share_token`),
KEY `user_id` (`user_id`),
KEY `series_id` (`series_id`),
KEY `idx_sort_order_in_series` (`sort_order_in_series`),
KEY `idx_books_series_id` (`series_id`),
KEY `idx_books_sort_order` (`sort_order_in_series`),
CONSTRAINT `books_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `books_ibfk_2` FOREIGN KEY (`series_id`) REFERENCES `series` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- Таблица глав
CREATE TABLE `chapters` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`book_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`word_count` int(11) DEFAULT 0,
`sort_order` int(11) NOT NULL DEFAULT 0,
`status` enum('draft','published') DEFAULT 'draft',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
KEY `book_id` (`book_id`),
CONSTRAINT `chapters_ibfk_1` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- Таблица сессий пользователей
CREATE TABLE `user_sessions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`session_token` varchar(64) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`expires_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `user_sessions_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
SET FOREIGN_KEY_CHECKS=1;
SQL;
$step = $_GET['step'] ?? '1';
$error = '';
$success = '';
// Обработка формы
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($step === '1') {
// Шаг 1: Проверка подключения к БД
$db_host = $_POST['db_host'] ?? 'localhost';
$db_name = $_POST['db_name'] ?? 'writer_app';
$db_user = $_POST['db_user'] ?? '';
$db_pass = $_POST['db_pass'] ?? '';
try {
// Пытаемся подключиться к MySQL
$pdo = new PDO("mysql:host=$db_host", $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Пытаемся создать базу данных если не существует
$pdo->exec("CREATE DATABASE IF NOT EXISTS `$db_name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$pdo->exec("USE `$db_name`");
// Сохраняем данные в сессии для следующего шага
session_start();
$_SESSION['install_data'] = [
'db_host' => $db_host,
'db_name' => $db_name,
'db_user' => $db_user,
'db_pass' => $db_pass
];
header('Location: install.php?step=2');
exit;
} catch (PDOException $e) {
$error = "Ошибка подключения к базе данных: " . $e->getMessage();
}
} elseif ($step === '2') {
// Шаг 2: Создание администратора
session_start();
if (!isset($_SESSION['install_data'])) {
header('Location: install.php?step=1');
exit;
}
$admin_username = $_POST['admin_username'] ?? '';
$admin_password = $_POST['admin_password'] ?? '';
$admin_email = $_POST['admin_email'] ?? '';
$admin_display_name = $_POST['admin_display_name'] ?? $admin_username;
if (empty($admin_username) || empty($admin_password)) {
$error = 'Имя пользователя и пароль администратора обязательны';
} else {
try {
$db = $_SESSION['install_data'];
$pdo = new PDO("mysql:host={$db['db_host']};dbname={$db['db_name']}", $db['db_user'], $db['db_pass']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Создаем таблицы
$pdo->exec($database_sql);
// Создаем администратора
$password_hash = password_hash($admin_password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("
INSERT INTO users (username, display_name, password_hash, email, is_active, created_at)
VALUES (?, ?, ?, ?, 1, NOW())
");
$stmt->execute([$admin_username, $admin_display_name, $password_hash, $admin_email]);
// Создаем config.php
$config_content = generate_config($db);
if (file_put_contents('config/config.php', $config_content)) {
// Создаем папки для загрузок
if (!file_exists('uploads/covers')) {
mkdir('uploads/covers', 0755, true);
}
if (!file_exists('uploads/avatars')) {
mkdir('uploads/avatars', 0755, true);
}
$success = 'Установка завершена успешно!';
session_destroy();
} else {
$error = 'Не удалось создать файл config.php. Проверьте права доступа к папке config/';
}
} catch (PDOException $e) {
$error = "Ошибка при установке: " . $e->getMessage();
}
}
}
}
function generate_config($db) {
$site_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
$base_path = str_replace('/install.php', '', $_SERVER['PHP_SELF']);
$site_url .= $base_path;
return <<<EOT
<?php
// config/config.php - автоматически сгенерирован установщиком
// Подключаем функции
require_once __DIR__ . '/../includes/functions.php';
session_start();
// Настройки базы данных
define('DB_HOST', '{$db['db_host']}');
define('DB_USER', '{$db['db_user']}');
define('DB_PASS', '{$db['db_pass']}');
define('DB_NAME', '{$db['db_name']}');
define('SITE_URL', '{$site_url}');
// Настройки приложения
define('APP_NAME', 'Web Writer');
define('UPLOAD_PATH', __DIR__ . '/../uploads/');
define('COVERS_PATH', UPLOAD_PATH . 'covers/');
define('COVERS_URL', SITE_URL . '/uploads/covers/');
define('AVATARS_PATH', UPLOAD_PATH . 'avatars/');
define('AVATARS_URL', SITE_URL . '/uploads/avatars/');
// Создаем папку для загрузок, если ее нет
if (!file_exists(COVERS_PATH)) {
mkdir(COVERS_PATH, 0755, true);
}
if (!file_exists(AVATARS_PATH)) {
mkdir(AVATARS_PATH, 0755, true);
}
// Подключение к базе данных
try {
\$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS);
\$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException \$e) {
error_log("DB Error: " . \$e->getMessage());
die("Ошибка подключения к базе данных");
}
// Автозагрузка моделей
spl_autoload_register(function (\$class_name) {
\$model_file = __DIR__ . '/../models/' . \$class_name . '.php';
if (file_exists(\$model_file)) {
require_once \$model_file;
}
});
?>
EOT;
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Установка Web Writer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1.5.10/css/pico.min.css">
<style>
.installation-steps {
display: flex;
justify-content: center;
margin-bottom: 2rem;
}
.step {
padding: 10px 20px;
margin: 0 10px;
border-radius: 20px;
background: #f0f0f0;
color: #666;
}
.step.active {
background: #007bff;
color: white;
}
.install-container {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
}
.alert {
padding: 1rem;
margin: 1rem 0;
border-radius: 5px;
}
.alert-error {
background: #ffebee;
color: #c62828;
border: 1px solid #ffcdd2;
}
.alert-success {
background: #e8f5e8;
color: #2e7d32;
border: 1px solid #c8e6c9;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 1rem;
}
.button-group a,
.button-group button {
flex: 1;
text-align: center;
padding: 0.75rem;
text-decoration: none;
border: 1px solid;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
box-sizing: border-box;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.button-group a {
background: var(--secondary);
border-color: var(--secondary);
color: var(--secondary-inverse);
}
.button-group button {
background: var(--primary);
border-color: var(--primary);
color: var(--primary-inverse);
}
.button-group a:hover,
.button-group button:hover {
opacity: 0.9;
}
</style>
</head>
<body>
<main class="container">
<div class="install-container">
<h1 style="text-align: center;">Установка Web Writer</h1>
<!-- Шаги установки -->
<div class="installation-steps">
<div class="step <?= $step === '1' ? 'active' : '' ?>">1. База данных</div>
<div class="step <?= $step === '2' ? 'active' : '' ?>">2. Администратор</div>
<div class="step <?= $step === '3' ? 'active' : '' ?>">3. Завершение</div>
</div>
<?php if ($error): ?>
<div class="alert alert-error">
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success">
<?= htmlspecialchars($success) ?>
<div style="margin-top: 1rem;">
<a href="index.php" role="button" class="contrast">Перейти к приложению</a>
</div>
</div>
<?php endif; ?>
<?php if (!$success): ?>
<?php if ($step === '1'): ?>
<!-- Шаг 1: Настройки базы данных -->
<form method="post">
<h3>Настройки базы данных</h3>
<label for="db_host">
Хост БД
<input type="text" id="db_host" name="db_host" value="<?= htmlspecialchars($_POST['db_host'] ?? 'localhost') ?>" required>
</label>
<label for="db_name">
Имя базы данных
<input type="text" id="db_name" name="db_name" value="<?= htmlspecialchars($_POST['db_name'] ?? 'writer_app') ?>" required>
</label>
<label for="db_user">
Пользователь БД
<input type="text" id="db_user" name="db_user" value="<?= htmlspecialchars($_POST['db_user'] ?? '') ?>" required>
</label>
<label for="db_pass">
Пароль БД
<input type="password" id="db_pass" name="db_pass" value="<?= htmlspecialchars($_POST['db_pass'] ?? '') ?>">
</label>
<button type="submit" class="contrast" style="width: 100%;">Продолжить</button>
</form>
<?php elseif ($step === '2'): ?>
<!-- Шаг 2: Создание администратора -->
<form method="post">
<h3>Создание администратора</h3>
<p>Создайте учетную запись администратора для управления приложением.</p>
<label for="admin_username">
Имя пользователя *
<input type="text" id="admin_username" name="admin_username" value="<?= htmlspecialchars($_POST['admin_username'] ?? '') ?>" required>
</label>
<label for="admin_password">
Пароль *
<input type="password" id="admin_password" name="admin_password" required>
</label>
<label for="admin_display_name">
Отображаемое имя
<input type="text" id="admin_display_name" name="admin_display_name" value="<?= htmlspecialchars($_POST['admin_display_name'] ?? '') ?>">
</label>
<label for="admin_email">
Email
<input type="email" id="admin_email" name="admin_email" value="<?= htmlspecialchars($_POST['admin_email'] ?? '') ?>">
</label>
<div class="button-group">
<a href="install.php?step=1">Назад</a>
<button type="submit">Завершить установку</button>
</div>
</form>
<?php endif; ?>
<?php endif; ?>
<?php if ($step === '1' && !$success): ?>
<div style="margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 5px;">
<h4>Перед установкой убедитесь, что:</h4>
<ul>
<li>Сервер MySQL запущен и доступен</li>
<li>У вас есть данные для подключения к БД (хост, пользователь, пароль)</li>
<li>Папка <code>config/</code> доступна для записи</li>
<li>Папка <code>uploads/</code> доступна для записи</li>
</ul>
</div>
<?php endif; ?>
</div>
</main>
</body>
</html>