#!/usr/bin/env python3 import hashlib from datetime import datetime, timezone import pymysql from config import MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE def hash_password(password: str) -> str: return hashlib.sha256(password.encode("utf-8")).hexdigest() def get_conn(database=None): return pymysql.connect( host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER, password=MYSQL_PASSWORD, database=database, charset="utf8mb4", autocommit=True, cursorclass=pymysql.cursors.DictCursor, ) def ensure_database(): conn = get_conn() try: with conn.cursor() as cur: cur.execute(f"CREATE DATABASE IF NOT EXISTS `{MYSQL_DATABASE}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") finally: conn.close() def ensure_tables_and_admin(): conn = get_conn(MYSQL_DATABASE) try: with conn.cursor() as cur: # Legacy migration: reader_pro -> user cur.execute("SHOW TABLES LIKE 'reader_pro'") has_legacy_user = bool(cur.fetchone()) cur.execute("SHOW TABLES LIKE 'user'") has_user = bool(cur.fetchone()) if has_legacy_user and not has_user: cur.execute("RENAME TABLE reader_pro TO user") # Legacy migration: reader_pro_progress -> user_progress cur.execute("SHOW TABLES LIKE 'reader_pro_progress'") has_legacy_progress = bool(cur.fetchone()) cur.execute("SHOW TABLES LIKE 'user_progress'") has_user_progress = bool(cur.fetchone()) if has_legacy_progress and not has_user_progress: cur.execute("RENAME TABLE reader_pro_progress TO user_progress") # Legacy migration: reader_pro_config -> user_config cur.execute("SHOW TABLES LIKE 'reader_pro_config'") has_legacy_config = bool(cur.fetchone()) cur.execute("SHOW TABLES LIKE 'user_config'") has_user_config = bool(cur.fetchone()) if has_legacy_config and not has_user_config: cur.execute("RENAME TABLE reader_pro_config TO user_config") cur.execute( """ CREATE TABLE IF NOT EXISTS user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, is_admin TINYINT(1) NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, session_token VARCHAR(128) NULL, session_expires_at DATETIME NULL, last_file VARCHAR(1024) NULL, last_page INT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 """ ) cur.execute("SHOW COLUMNS FROM user LIKE 'is_active'") if not cur.fetchone(): cur.execute("ALTER TABLE user ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER is_admin") cur.execute( """ CREATE TABLE IF NOT EXISTS user_progress ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, file_path VARCHAR(512) NOT NULL, page INT NOT NULL, updated_at DATETIME NOT NULL, UNIQUE KEY uniq_user_file (user_id, file_path), KEY idx_user_updated (user_id, updated_at), CONSTRAINT fk_user_progress_user FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 """ ) cur.execute( """ CREATE TABLE IF NOT EXISTS user_config ( config_key VARCHAR(128) PRIMARY KEY, config_value TEXT NULL, updated_at DATETIME NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 """ ) now = datetime.now(timezone.utc).replace(tzinfo=None) admin_hash = hash_password("admin123") cur.execute("SELECT id FROM user WHERE username=%s", ("admin",)) row = cur.fetchone() if row: cur.execute( """ UPDATE user SET password_hash=%s, is_admin=1, is_active=1, updated_at=%s WHERE username=%s """, (admin_hash, now, "admin"), ) admin_action = "updated" else: cur.execute( """ INSERT INTO user (username, password_hash, is_admin, is_active, created_at, updated_at) VALUES (%s, %s, 1, 1, %s, %s) """, ("admin", admin_hash, now, now), ) admin_action = "created" finally: conn.close() return admin_action def main(): ensure_database() admin_action = ensure_tables_and_admin() print(f"[OK] Database `{MYSQL_DATABASE}` initialized.") print("[OK] Tables: user, user_progress, user_config") print(f"[OK] Admin user `admin` {admin_action}, password set to `admin123`") if __name__ == "__main__": main()