Добавление модуля загрузки категорий для API ВОЛК и соответствующей кнопки на фронте
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2025-11-14 14:00:29 +03:00
parent c6642f6cda
commit 41ccd110ba
3 changed files with 217 additions and 0 deletions
+21
View File
@@ -24,6 +24,7 @@ from tg_publish import publish_to_tg_all, publish_to_tg
from db_update_shortname import ai_shortname_all, generate_ai_shortname
from evtg_publish import tg_post_event_by_id, tg_post_all_events
from zk_load import load_json_all
from volk_load import load_categories
# Загружаем настройки из .env
load_dotenv()
@@ -1064,6 +1065,26 @@ def api_publish_all_events():
log_event(error_msg)
return jsonify({'success': False, 'error': error_msg}), 500
@bp.route('/api/load_categories', methods=['POST'])
@login_required
def api_load_categories():
try:
# Проверка режима работы
workmode = os.getenv('WORKMODE', 'ZILANT')
if workmode != 'VOLK':
error_msg = "Загрузка категорий доступна только в режиме VOLK"
log_event(error_msg)
return jsonify({'success': False, 'error': error_msg}), 403
log_event("Запуск загрузки категорий...")
load_categories()
log_event("Загрузка категорий выполнена успешно")
return jsonify({'success': True})
except Exception as e:
error_msg = f"Ошибка при загрузке категорий: {str(e)}"
log_event(error_msg)
return jsonify({'success': False, 'error': error_msg}), 500
@bp.route('/api/publish_event/<int:event_id>', methods=['POST'])
@login_required
def api_publish_event(event_id):
+36
View File
@@ -566,6 +566,11 @@
<button class="btn btn-success" id="publish-all-events-btn">
<i class="fas fa-paper-plane me-1"></i> Опубликовать все
</button>
{% if g.workmode == 'VOLK' %}
<button class="btn btn-info ms-2" id="load-categories-btn">
<i class="fas fa-sync-alt me-1"></i> Категории
</button>
{% endif %}
<div id="event-selected-post-info" class="cross-tab-info" style="display: none;">
<span>Выбранный пост: </span>
<span id="event-selected-post-name"></span>
@@ -857,6 +862,12 @@
// Новые обработчики для кнопок событий
document.getElementById('zk-rescan-btn').addEventListener('click', runZkRescan);
document.getElementById('publish-all-events-btn').addEventListener('click', publishAllEvents);
{% if g.workmode == 'VOLK' %}
const loadCategoriesBtn = document.getElementById('load-categories-btn');
if (loadCategoriesBtn) {
loadCategoriesBtn.addEventListener('click', loadCategories);
}
{% endif %}
// Обработчики для кнопок привязки
document.getElementById('post-link-event-btn').addEventListener('click', togglePostEventLink);
document.getElementById('event-link-post-btn').addEventListener('click', togglePostEventLink);
@@ -977,6 +988,31 @@
alert('Произошла ошибка при публикации всех событий');
});
}
// Функция для загрузки категорий
function loadCategories() {
showProcessingMessage("Загрузка категорий...");
fetch(`${PREFIX}/api/load_categories`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
hideProcessingMessage();
if (data.success) {
alert('Категории загружены успешно!');
} else {
alert('Ошибка загрузки категорий: ' + (data.error || 'неизвестная ошибка'));
}
})
.catch(error => {
hideProcessingMessage();
console.error('Ошибка загрузки категорий:', error);
alert('Произошла ошибка при загрузке категорий');
});
}
// Функция для публикации конкретного события
function publishCurrentEvent(eventId) {
if (!eventId) return;
+160
View File
@@ -0,0 +1,160 @@
import os
import json
import time
from datetime import datetime
from urllib.request import urlopen
from dotenv import load_dotenv
import mysql.connector
from mysql.connector import Error
from ensure_db import ensure_database_structure
# Загрузка переменных окружения
load_dotenv()
VOLK_CATEGORY_LIST = os.getenv('VOLK_CATEGORY_LIST')
VOLK_API_VERSION = os.getenv('VOLK_API_VERSION')
MDB_HOST = os.getenv('MDB_HOST')
MDB_USER = os.getenv('MDB_USER')
MDB_PW = os.getenv('MDB_PW')
MDBASE = os.getenv('MDBASE')
LOG_FILE = os.getenv('LOG_FILE')
def log_message(message, category="VOLK_loader"):
"""Функция для записи в лог-файл с повторными попытками при блокировке"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] [{category}] {message}\n"
# До 5 попыток записи с увеличением задержки
for attempt in range(5):
try:
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(log_entry)
return True
except (IOError, OSError) as e:
if attempt < 4: # Не последняя попытка
time.sleep(0.4) # Задержка 400ms между попытками
else:
# После 5 неудачных попыток выводим в stdout
print(f"Не удалось записать в лог после 5 попыток: {log_entry.strip()}")
return False
return False
def load_categories():
"""Экспортируемая функция для загрузки категорий из JSON в базу данных"""
inserted_count = 0
updated_count = 0
try:
log_message("Начало работы")
# Проверка наличия URL
if not VOLK_CATEGORY_LIST:
error_msg = "Переменная окружения VOLK_CATEGORY_LIST не установлена"
log_message(error_msg, "ERROR")
print(error_msg)
return
# Чтение JSON из URL
log_message(f"Чтение данных из {VOLK_CATEGORY_LIST}")
with urlopen(VOLK_CATEGORY_LIST) as response:
data = json.loads(response.read().decode())
# Проверка версии API
api_version = data.get('api_version')
if api_version != VOLK_API_VERSION:
error_msg = f"Несовпадение версии API: получена версия '{api_version}', ожидается '{VOLK_API_VERSION}'. Обработка остановлена."
log_message(error_msg, "ERROR")
print(error_msg)
return
log_message(f"Версия API соответствует: {api_version}")
# Проверка наличия блока data
if 'data' not in data:
error_msg = "В JSON отсутствует блок 'data'"
log_message(error_msg, "ERROR")
print(error_msg)
return
categories_data = data['data']
log_message(f"Получено {len(categories_data)} записей для обработки")
# Подключение к базе данных
connection = mysql.connector.connect(
host=MDB_HOST,
user=MDB_USER,
password=MDB_PW,
database=MDBASE
)
# Установка временной зоны соединения в UTC
cursor_temp = connection.cursor()
cursor_temp.execute("SET time_zone = '+00:00'")
cursor_temp.close()
cursor = connection.cursor(dictionary=True)
# Проверка структуры БД
log_message("Проверка структуры базы данных")
ensure_database_structure()
for item in categories_data:
# Проверка обязательного поля ID
if 'id' not in item or not item['id']:
log_message(f"Пропущена запись без id или с пустым id: {item}", "WARNING")
continue
# Подготовка значений
category_id = item['id']
# TITLE может отсутствовать или быть пустым - в этом случае используем пустую строку
title = item.get('title', '') or ''
# Проверка существующей записи
cursor.execute(
"SELECT AUTO_ID, ID, TITLE FROM categories WHERE ID = %s",
(category_id,)
)
existing = cursor.fetchone()
if not existing:
# Вставка новой записи
cursor.execute(
"INSERT INTO categories (ID, TITLE) VALUES (%s, %s)",
(category_id, title)
)
inserted_count += 1
log_message(f"Добавление: Категория {category_id}, Название: {title if title else '(пусто)'}")
else:
# Обновление существующей записи (всегда обновляем TITLE, даже если оно пустое)
if existing['TITLE'] != title:
cursor.execute(
"UPDATE categories SET TITLE = %s WHERE ID = %s",
(title, category_id)
)
updated_count += 1
log_message(f"Обновление: Категория {category_id}, Новое название: {title if title else '(пусто)'}")
else:
log_message(f"Категория {category_id} без изменений")
connection.commit()
log_message(f"Завершение работы. Добавлено записей: {inserted_count}. Обновлено записей: {updated_count}.")
except Error as e:
error_msg = f"Ошибка базы данных: {e}"
log_message(error_msg, "ERROR")
print(error_msg)
if 'connection' in locals() and connection.is_connected():
connection.rollback()
except Exception as e:
error_msg = f"Общая ошибка: {e}"
log_message(error_msg, "ERROR")
print(error_msg)
finally:
if 'connection' in locals() and connection.is_connected():
cursor.close()
connection.close()
if __name__ == "__main__":
load_categories()