Добавление модуля загрузки категорий для API ВОЛК и соответствующей кнопки на фронте
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
+21
@@ -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 db_update_shortname import ai_shortname_all, generate_ai_shortname
|
||||||
from evtg_publish import tg_post_event_by_id, tg_post_all_events
|
from evtg_publish import tg_post_event_by_id, tg_post_all_events
|
||||||
from zk_load import load_json_all
|
from zk_load import load_json_all
|
||||||
|
from volk_load import load_categories
|
||||||
|
|
||||||
# Загружаем настройки из .env
|
# Загружаем настройки из .env
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -1064,6 +1065,26 @@ def api_publish_all_events():
|
|||||||
log_event(error_msg)
|
log_event(error_msg)
|
||||||
return jsonify({'success': False, 'error': error_msg}), 500
|
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'])
|
@bp.route('/api/publish_event/<int:event_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def api_publish_event(event_id):
|
def api_publish_event(event_id):
|
||||||
|
|||||||
@@ -566,6 +566,11 @@
|
|||||||
<button class="btn btn-success" id="publish-all-events-btn">
|
<button class="btn btn-success" id="publish-all-events-btn">
|
||||||
<i class="fas fa-paper-plane me-1"></i> Опубликовать все
|
<i class="fas fa-paper-plane me-1"></i> Опубликовать все
|
||||||
</button>
|
</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;">
|
<div id="event-selected-post-info" class="cross-tab-info" style="display: none;">
|
||||||
<span>Выбранный пост: </span>
|
<span>Выбранный пост: </span>
|
||||||
<span id="event-selected-post-name"></span>
|
<span id="event-selected-post-name"></span>
|
||||||
@@ -857,6 +862,12 @@
|
|||||||
// Новые обработчики для кнопок событий
|
// Новые обработчики для кнопок событий
|
||||||
document.getElementById('zk-rescan-btn').addEventListener('click', runZkRescan);
|
document.getElementById('zk-rescan-btn').addEventListener('click', runZkRescan);
|
||||||
document.getElementById('publish-all-events-btn').addEventListener('click', publishAllEvents);
|
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('post-link-event-btn').addEventListener('click', togglePostEventLink);
|
||||||
document.getElementById('event-link-post-btn').addEventListener('click', togglePostEventLink);
|
document.getElementById('event-link-post-btn').addEventListener('click', togglePostEventLink);
|
||||||
@@ -977,6 +988,31 @@
|
|||||||
alert('Произошла ошибка при публикации всех событий');
|
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) {
|
function publishCurrentEvent(eventId) {
|
||||||
if (!eventId) return;
|
if (!eventId) return;
|
||||||
|
|||||||
+160
@@ -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()
|
||||||
|
|
||||||
Reference in New Issue
Block a user