diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..8c346de
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,6 @@
+RewriteEngine On
+
+
+ Order Allow,Deny
+ Deny from all
+
\ No newline at end of file
diff --git a/README b/README
index 01e661a..9ab4394 100644
--- a/README
+++ b/README
@@ -47,10 +47,17 @@ Docker установка:
- Стиль 4chan/2ch
- Ключ доступа для входа
- Автообновление постов
+- Админка для управления тредами
Безопасность:
- Валидация файлов
- Ограничение размера
- Защита от XSS
- Rate limiting
-- Безопасная загрузка файлов
\ No newline at end of file
+- Безопасная загрузка файлов
+
+Админка:
+- Доступ по паролю из конфига
+- Удаление тредов
+- Просмотр статистики
+- Путь: /admin1337.php
\ No newline at end of file
diff --git a/admin.php b/admin.php
new file mode 100644
index 0000000..9bc93eb
--- /dev/null
+++ b/admin.php
@@ -0,0 +1,420 @@
+
+
+
+
+
+
+ mkach - Админка
+
+
+
+
+
mkach - Админка
+
+
+
= htmlspecialchars($error) ?>
+
+
+
+
+ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $db->exec('SET NAMES utf8');
+} catch (PDOException $e) {
+ die('Connection failed');
+}
+
+require_once 'logger.php';
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_thread'])) {
+ $threadId = $_POST['delete_thread'];
+
+ try {
+ $db->beginTransaction();
+
+ $stmt = $db->prepare('DELETE FROM posts WHERE thread_id = ?');
+ $stmt->execute([$threadId]);
+
+ $stmt = $db->prepare('DELETE FROM threads WHERE thread_id = ?');
+ $stmt->execute([$threadId]);
+
+ $db->commit();
+ logAdminAction('DELETE_THREAD', "Thread ID: $threadId");
+ $success = 'Тред успешно удален';
+ } catch (PDOException $e) {
+ $db->rollBack();
+ $error = 'Ошибка при удалении треда';
+ }
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_post'])) {
+ $postId = $_POST['delete_post'];
+
+ if (!preg_match('/^\d{6}$/', $postId)) {
+ $error = 'Неверный формат номера сообщения';
+ } else {
+ try {
+ $stmt = $db->prepare('SELECT COUNT(*) FROM posts WHERE post_id = ?');
+ $stmt->execute([$postId]);
+ $exists = $stmt->fetchColumn();
+
+ if ($exists) {
+ $stmt = $db->prepare('DELETE FROM posts WHERE post_id = ?');
+ $stmt->execute([$postId]);
+ logAdminAction('DELETE_POST', "Post ID: $postId");
+ $success = 'Сообщение №' . $postId . ' успешно удалено';
+ } else {
+ $error = 'Сообщение №' . $postId . ' не найдено';
+ }
+ } catch (PDOException $e) {
+ $error = 'Ошибка при удалении сообщения';
+ }
+ }
+}
+
+try {
+ $stmt = $db->prepare('
+ SELECT t.*, b.name as board_name, COUNT(p.id) as post_count
+ FROM threads t
+ JOIN boards b ON t.board_id = b.board_id
+ LEFT JOIN posts p ON t.thread_id = p.thread_id
+ GROUP BY t.id
+ ORDER BY t.created_at DESC
+ ');
+ $stmt->execute();
+ $threads = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ $stmt = $db->prepare('
+ SELECT p.*, t.title as thread_title, b.board_id
+ FROM posts p
+ JOIN threads t ON p.thread_id = t.thread_id
+ JOIN boards b ON t.board_id = b.board_id
+ ORDER BY p.created_at DESC
+ LIMIT 50
+ ');
+ $stmt->execute();
+ $recentPosts = $stmt->fetchAll(PDO::FETCH_ASSOC);
+} catch (PDOException $e) {
+ die('Database error');
+}
+?>
+
+
+
+
+
+ mkach - Админка
+
+
+
+
+
+
+
+
= htmlspecialchars($success) ?>
+
+
+
+
= htmlspecialchars($error) ?>
+
+
+
Управление тредами
+
+
+
+ ID |
+ Доска |
+ Название |
+ Постов |
+ Создан |
+ Действия |
+
+
+
+
+
+ = htmlspecialchars($thread['thread_id']) ?> |
+ /= htmlspecialchars($thread['board_id']) ?>/ |
+ = htmlspecialchars($thread['title'] ?? 'Без названия') ?> |
+ = $thread['post_count'] ?> |
+ = date('d.m.Y H:i', strtotime($thread['created_at'])) ?> |
+
+
+ |
+
+
+
+
+
+
Удаление сообщений
+
+
+
Последние сообщения
+
+
+
+ № |
+ Тред |
+ Доска |
+ Сообщение |
+ Время |
+
+
+
+
+
+ = htmlspecialchars($post['post_id']) ?> |
+ = htmlspecialchars($post['thread_title'] ?? 'Без названия') ?> |
+ /= htmlspecialchars($post['board_id']) ?>/ |
+
+ 50 ? substr($message, 0, 50) . '...' : $message;
+ ?>
+ |
+ = date('d.m.Y H:i', strtotime($post['created_at'])) ?> |
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin1337.php b/admin1337.php
new file mode 100644
index 0000000..9bc93eb
--- /dev/null
+++ b/admin1337.php
@@ -0,0 +1,420 @@
+
+
+
+
+
+
+ mkach - Админка
+
+
+
+
+
mkach - Админка
+
+
+
= htmlspecialchars($error) ?>
+
+
+
+
+ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $db->exec('SET NAMES utf8');
+} catch (PDOException $e) {
+ die('Connection failed');
+}
+
+require_once 'logger.php';
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_thread'])) {
+ $threadId = $_POST['delete_thread'];
+
+ try {
+ $db->beginTransaction();
+
+ $stmt = $db->prepare('DELETE FROM posts WHERE thread_id = ?');
+ $stmt->execute([$threadId]);
+
+ $stmt = $db->prepare('DELETE FROM threads WHERE thread_id = ?');
+ $stmt->execute([$threadId]);
+
+ $db->commit();
+ logAdminAction('DELETE_THREAD', "Thread ID: $threadId");
+ $success = 'Тред успешно удален';
+ } catch (PDOException $e) {
+ $db->rollBack();
+ $error = 'Ошибка при удалении треда';
+ }
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_post'])) {
+ $postId = $_POST['delete_post'];
+
+ if (!preg_match('/^\d{6}$/', $postId)) {
+ $error = 'Неверный формат номера сообщения';
+ } else {
+ try {
+ $stmt = $db->prepare('SELECT COUNT(*) FROM posts WHERE post_id = ?');
+ $stmt->execute([$postId]);
+ $exists = $stmt->fetchColumn();
+
+ if ($exists) {
+ $stmt = $db->prepare('DELETE FROM posts WHERE post_id = ?');
+ $stmt->execute([$postId]);
+ logAdminAction('DELETE_POST', "Post ID: $postId");
+ $success = 'Сообщение №' . $postId . ' успешно удалено';
+ } else {
+ $error = 'Сообщение №' . $postId . ' не найдено';
+ }
+ } catch (PDOException $e) {
+ $error = 'Ошибка при удалении сообщения';
+ }
+ }
+}
+
+try {
+ $stmt = $db->prepare('
+ SELECT t.*, b.name as board_name, COUNT(p.id) as post_count
+ FROM threads t
+ JOIN boards b ON t.board_id = b.board_id
+ LEFT JOIN posts p ON t.thread_id = p.thread_id
+ GROUP BY t.id
+ ORDER BY t.created_at DESC
+ ');
+ $stmt->execute();
+ $threads = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ $stmt = $db->prepare('
+ SELECT p.*, t.title as thread_title, b.board_id
+ FROM posts p
+ JOIN threads t ON p.thread_id = t.thread_id
+ JOIN boards b ON t.board_id = b.board_id
+ ORDER BY p.created_at DESC
+ LIMIT 50
+ ');
+ $stmt->execute();
+ $recentPosts = $stmt->fetchAll(PDO::FETCH_ASSOC);
+} catch (PDOException $e) {
+ die('Database error');
+}
+?>
+
+
+
+
+
+ mkach - Админка
+
+
+
+
+
+
+
+
= htmlspecialchars($success) ?>
+
+
+
+
= htmlspecialchars($error) ?>
+
+
+
Управление тредами
+
+
+
+ ID |
+ Доска |
+ Название |
+ Постов |
+ Создан |
+ Действия |
+
+
+
+
+
+ = htmlspecialchars($thread['thread_id']) ?> |
+ /= htmlspecialchars($thread['board_id']) ?>/ |
+ = htmlspecialchars($thread['title'] ?? 'Без названия') ?> |
+ = $thread['post_count'] ?> |
+ = date('d.m.Y H:i', strtotime($thread['created_at'])) ?> |
+
+
+ |
+
+
+
+
+
+
Удаление сообщений
+
+
+
Последние сообщения
+
+
+
+ № |
+ Тред |
+ Доска |
+ Сообщение |
+ Время |
+
+
+
+
+
+ = htmlspecialchars($post['post_id']) ?> |
+ = htmlspecialchars($post['thread_title'] ?? 'Без названия') ?> |
+ /= htmlspecialchars($post['board_id']) ?>/ |
+
+ 50 ? substr($message, 0, 50) . '...' : $message;
+ ?>
+ |
+ = date('d.m.Y H:i', strtotime($post['created_at'])) ?> |
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/board.php b/board.php
index b657e30..f03bdec 100644
--- a/board.php
+++ b/board.php
@@ -45,6 +45,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
+ if (strlen($message) > $config['max_message_length']) {
+ $error = 'Сообщение слишком длинное';
+ }
+
if ($message || $title || ($file && $file['error'] === UPLOAD_ERR_OK)) {
$postId = sprintf('%06d', mt_rand(1, 999999));
diff --git a/config.php b/config.php
index 0299e87..5ef07b8 100644
--- a/config.php
+++ b/config.php
@@ -10,7 +10,11 @@ return [
'upload_path' => 'uploads/',
'max_file_size' => 26214400,
'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'webp','JPG','JPEG','PNG','GIF','WEBP'],
+ 'max_message_length' => 10000,
'motd' => 'Добро пожаловать на mkach - анонимный имиджборд от МК',
'logo_enabled' => true,
- 'logo_text' => 'mkach'
+ 'logo_text' => 'mkach',
+ 'admin' => [
+ 'password' => 'admin1337'
+ ]
];
\ No newline at end of file
diff --git a/config.php.example b/config.php.example
index 59fcc91..36a98b2 100644
--- a/config.php.example
+++ b/config.php.example
@@ -10,7 +10,11 @@ return [
'upload_path' => 'uploads/',
'max_file_size' => 26214400,
'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'webp','JPG','JPEG','PNG','GIF','WEBP'],
+ 'max_message_length' => 10000,
'motd' => 'Добро пожаловать на mkach - анонимный имиджборд от МК',
'logo_enabled' => true,
- 'logo_text' => 'mkach'
+ 'logo_text' => 'mkach',
+ 'admin' => [
+ 'password' => 'admin1337'
+ ]
];
\ No newline at end of file
diff --git a/csrf.php b/csrf.php
new file mode 100644
index 0000000..d0886bb
--- /dev/null
+++ b/csrf.php
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/logger.php b/logger.php
new file mode 100644
index 0000000..e8239c2
--- /dev/null
+++ b/logger.php
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/styles.css b/styles.css
index ef7b980..b1b03d6 100644
--- a/styles.css
+++ b/styles.css
@@ -43,7 +43,7 @@ body {
color: #ff6b6b;
}
-.boards-btn, .logout-btn {
+.boards-btn, .logout-btn, .admin-btn {
background: #d6daf0;
border: 1px solid #b7c5d9;
padding: 5px 10px;
@@ -53,7 +53,7 @@ body {
margin-left: 5px;
}
-.boards-btn:hover, .logout-btn:hover {
+.boards-btn:hover, .logout-btn:hover, .admin-btn:hover {
background: #e5e9f0;
}