Files
Zilant2025/ensure_db.py
T

332 lines
14 KiB
Python

# ensure_db.py
import mysql.connector
import os
import time
import random
from datetime import datetime
from mysql.connector import errorcode
from dotenv import load_dotenv
from werkzeug.security import generate_password_hash
# Загрузка переменных окружения
load_dotenv()
# Конфигурация из переменных окружения
DB_CONFIG = {
'host': os.getenv('MDB_HOST', 'localhost'),
'user': os.getenv('MDB_USER', 'root'),
'password': os.getenv('MDB_PW', ''),
'database': os.getenv('MDBASE', 'vk_posts')
}
LOG_FILE = os.getenv('LOG_FILE', 'vk_loader.log')
LOG_PREFIX = "DB_Ensure"
def log_message(message, max_retries=5, retry_delay=0.1):
"""Функция логирования с повторными попытками"""
if not LOG_FILE:
return False
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_line = f"[{timestamp}] [{LOG_PREFIX}] {message}\n"
for attempt in range(max_retries):
try:
with open(LOG_FILE, 'a', encoding='utf-8') as log:
log.write(log_line)
return True
except (IOError, OSError) as e:
if attempt < max_retries - 1:
time.sleep(retry_delay * (1 + random.random()))
else:
print(f"Failed to log: {e}")
return False
def ensure_database_structure():
"""Основная функция проверки и создания БД/таблиц"""
conn = None
cursor = None
try:
# Подключение без указания базы данных
conn = mysql.connector.connect(
host=DB_CONFIG['host'],
user=DB_CONFIG['user'],
password=DB_CONFIG['password']
)
cursor = conn.cursor(buffered=True) # Используем буферизованный курсор
# Создание базы данных если не существует
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {DB_CONFIG['database']} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
cursor.execute(f"USE {DB_CONFIG['database']}")
log_message("Database checked/created successfully")
# Список таблиц и их структур
tables = {
'posts': '''
CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
vk_post_id BIGINT NOT NULL UNIQUE,
text TEXT,
image_url VARCHAR(2048),
vk_post_url VARCHAR(2048),
published_at DATETIME NULL,
is_poll BOOLEAN DEFAULT 0,
poll_question TEXT,
poll_options TEXT,
poll_multiple BOOLEAN DEFAULT 0,
poll_end_date DATETIME NULL,
marked_for_publication BOOLEAN DEFAULT 0,
published_in_tg BOOLEAN DEFAULT 0,
tg_publication_date DATETIME NULL,
tg_message_id BIGINT NULL,
tg_poll_id BIGINT NULL,
tg_markpost_id BIGINT NULL,
tg_poll_results TEXT DEFAULT '',
is_event BOOLEAN DEFAULT 0,
shortname VARCHAR(100),
action_number BIGINT NULL,
auto_unit VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
''',
'marks': '''
CREATE TABLE marks (
id INT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tg_post_id BIGINT NOT NULL,
tg_user_id BIGINT NOT NULL,
tg_user_name VARCHAR(255),
autolink BOOLEAN DEFAULT 0,
reminded_at DATETIME NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
''',
'marks_evt': '''
CREATE TABLE marks_evt (
id INT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tg_event_id BIGINT NOT NULL,
tg_user_id BIGINT NOT NULL,
tg_user_name VARCHAR(255),
autolink BOOLEAN DEFAULT 0,
reminded_at DATETIME NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
''',
'users': '''
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
''',
'events': '''
CREATE TABLE events (
id INT AUTO_INCREMENT PRIMARY KEY,
id_event BIGINT NOT NULL,
number BIGINT NOT NULL,
unit_name VARCHAR(255),
name VARCHAR(255),
about VARCHAR(4096),
about_social_picture VARCHAR(2048),
authors VARCHAR(255),
modified DATETIME NULL,
added DATETIME NULL,
tags VARCHAR(255),
is_canceled BOOLEAN DEFAULT 0,
accepted BOOLEAN DEFAULT 0,
denied BOOLEAN DEFAULT 0,
is_visible BOOLEAN DEFAULT 0,
marked_to_publication BOOLEAN DEFAULT 0,
is_posted_tg BOOLEAN DEFAULT 0,
tg_message_id BIGINT NULL,
tg_info_id BIGINT NULL,
tg_posted_date DATETIME NULL,
announcement_link BIGINT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
''',
'interlinks': '''
CREATE TABLE interlinks (
id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT NOT NULL,
event_id INT NOT NULL,
created_at DATETIME NULL,
auto_added BOOLEAN DEFAULT 0,
FOREIGN KEY (post_id)
REFERENCES posts(id)
ON DELETE CASCADE,
FOREIGN KEY (event_id)
REFERENCES events(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
''',
'categories': '''
CREATE TABLE categories (
AUTO_ID INT AUTO_INCREMENT PRIMARY KEY,
ID VARCHAR(255),
TITLE VARCHAR(2048),
description TEXT,
tg_id BIGINT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
'''
}
# Проверка и создание таблиц
for table_name, table_sql in tables.items():
try:
# Проверяем существование таблицы через information_schema
cursor.execute("""
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = %s AND table_name = %s
""", (DB_CONFIG['database'], table_name))
if cursor.fetchone()[0] == 0:
# Таблица не существует, создаем её
cursor.execute(table_sql)
log_message(f"Table {table_name} created")
else:
log_message(f"Table {table_name} exists")
except mysql.connector.Error as err:
log_message(f"Error checking/creating table {table_name}: {err}")
raise err
# Создание дефолтного админа, если таблица users пуста
try:
cursor.execute("SELECT COUNT(*) as count FROM users")
user_count = cursor.fetchone()[0]
if user_count == 0:
username = os.getenv('DEFNM')
password = os.getenv('DEFPW')
if username and password:
hashed_password = generate_password_hash(password)
cursor.execute(
"INSERT INTO users (username, password) VALUES (%s, %s)",
(username, hashed_password)
)
log_message(f"Created default admin user: {username}")
else:
log_message("Environment variables DEFNM/DEFPW not set. Admin not created.")
except mysql.connector.Error as err:
log_message(f"Error creating default admin: {err}")
# Проверка и добавление новых полей в существующие таблицы
table_alterations = {
'posts': [
{'column': 'auto_unit', 'type': 'VARCHAR(255)'}
],
'marks': [
{'column': 'autolink', 'type': 'BOOLEAN DEFAULT 0'},
{'column': 'reminded_at', 'type': 'DATETIME NULL'}
],
'marks_evt': [
{'column': 'autolink', 'type': 'BOOLEAN DEFAULT 0'},
{'column': 'reminded_at', 'type': 'DATETIME NULL'}
],
'interlinks': [
{'column': 'auto_added', 'type': 'BOOLEAN DEFAULT 0'}
],
'categories': [
{'column': 'description', 'type': 'TEXT'},
{'column': 'tg_id', 'type': 'BIGINT NULL'}
],
'events': [
{'column': 'authors', 'type': 'VARCHAR(255)'}
]
}
for table_name, alterations in table_alterations.items():
for alteration in alterations:
column = alteration['column']
column_type = alteration['type']
try:
# Проверяем существование столбца
cursor.execute("""
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = %s AND table_name = %s AND column_name = %s
""", (DB_CONFIG['database'], table_name, column))
if cursor.fetchone()[0] == 0:
# Столбец не существует, добавляем его
cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN {column} {column_type}")
log_message(f"Added column {column} to table {table_name}")
else:
log_message(f"Column {column} already exists in table {table_name}")
except mysql.connector.Error as err:
log_message(f"Error checking/adding column {column} to table {table_name}: {err}")
# Создание индексов для таблицы marks
marks_indexes = [
'CREATE UNIQUE INDEX IF NOT EXISTS uniq_post_user ON marks (tg_post_id, tg_user_id)',
'CREATE INDEX IF NOT EXISTS idx_post_subscribers ON marks (tg_post_id)',
'CREATE INDEX IF NOT EXISTS idx_user_subscriptions ON marks (tg_user_id)',
'CREATE INDEX IF NOT EXISTS idx_created_at ON marks (created_at)',
'CREATE INDEX IF NOT EXISTS idx_post_created ON marks (tg_post_id, created_at)',
'CREATE INDEX IF NOT EXISTS idx_tg_post_id ON marks (tg_post_id)'
]
# Создание индексов для таблицы marks_evt
marks_evt_indexes = [
'CREATE UNIQUE INDEX IF NOT EXISTS uniq_event_user ON marks_evt (tg_event_id, tg_user_id)',
'CREATE INDEX IF NOT EXISTS idx_event_subscribers ON marks_evt (tg_event_id)',
'CREATE INDEX IF NOT EXISTS idx_user_event_subscriptions ON marks_evt (tg_user_id)',
'CREATE INDEX IF NOT EXISTS idx_marks_evt_created_at ON marks_evt (created_at)',
'CREATE INDEX IF NOT EXISTS idx_event_created ON marks_evt (tg_event_id, created_at)',
'CREATE INDEX IF NOT EXISTS idx_tg_event_id ON marks_evt (tg_event_id)'
]
# Создание индексов для таблицы events
events_indexes = [
'CREATE UNIQUE INDEX IF NOT EXISTS idx_events_id_event ON events (id_event)',
'CREATE INDEX IF NOT EXISTS idx_events_number ON events (number)',
'CREATE INDEX IF NOT EXISTS idx_events_is_visible ON events (is_visible)',
'CREATE INDEX IF NOT EXISTS idx_events_is_posted_tg ON events (is_posted_tg)',
]
# Создание индексов для таблицы interlinks
interlinks_indexes = [
'CREATE INDEX IF NOT EXISTS idx_interlinks_post_id ON interlinks (post_id)',
'CREATE INDEX IF NOT EXISTS idx_interlinks_event_id ON interlinks (event_id)',
'CREATE UNIQUE INDEX IF NOT EXISTS uniq_post_event ON interlinks (post_id, event_id)'
]
# Создание индексов для таблицы categories
categories_indexes = [
'CREATE INDEX IF NOT EXISTS idx_categories_id ON categories (ID)'
]
# Объединяем все индексы
all_indexes = marks_indexes + marks_evt_indexes + events_indexes + interlinks_indexes + categories_indexes
for index_sql in all_indexes:
try:
cursor.execute(index_sql)
index_name = index_sql.split('IF NOT EXISTS')[1].split(' ON ')[0].strip()
log_message(f"Index created: {index_name}")
except mysql.connector.Error as err:
log_message(f"Error creating index: {err}")
conn.commit()
log_message("Database structure ensured successfully")
except mysql.connector.Error as err:
log_message(f"Database error: {err}")
raise
finally:
# Аккуратно закрываем соединение и курсор
try:
if cursor:
cursor.close()
except:
pass
try:
if conn and conn.is_connected():
conn.close()
except:
pass
if __name__ == "__main__":
ensure_database_structure()