init_db.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/usr/bin/env python3
  2. import hashlib
  3. from datetime import datetime, timezone
  4. import pymysql
  5. from config import MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE
  6. def hash_password(password: str) -> str:
  7. return hashlib.sha256(password.encode("utf-8")).hexdigest()
  8. def get_conn(database=None):
  9. return pymysql.connect(
  10. host=MYSQL_HOST,
  11. port=MYSQL_PORT,
  12. user=MYSQL_USER,
  13. password=MYSQL_PASSWORD,
  14. database=database,
  15. charset="utf8mb4",
  16. autocommit=True,
  17. cursorclass=pymysql.cursors.DictCursor,
  18. )
  19. def ensure_database():
  20. conn = get_conn()
  21. try:
  22. with conn.cursor() as cur:
  23. cur.execute(f"CREATE DATABASE IF NOT EXISTS `{MYSQL_DATABASE}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
  24. finally:
  25. conn.close()
  26. def ensure_tables_and_admin():
  27. conn = get_conn(MYSQL_DATABASE)
  28. try:
  29. with conn.cursor() as cur:
  30. # Legacy migration: reader_pro -> user
  31. cur.execute("SHOW TABLES LIKE 'reader_pro'")
  32. has_legacy_user = bool(cur.fetchone())
  33. cur.execute("SHOW TABLES LIKE 'user'")
  34. has_user = bool(cur.fetchone())
  35. if has_legacy_user and not has_user:
  36. cur.execute("RENAME TABLE reader_pro TO user")
  37. # Legacy migration: reader_pro_progress -> user_progress
  38. cur.execute("SHOW TABLES LIKE 'reader_pro_progress'")
  39. has_legacy_progress = bool(cur.fetchone())
  40. cur.execute("SHOW TABLES LIKE 'user_progress'")
  41. has_user_progress = bool(cur.fetchone())
  42. if has_legacy_progress and not has_user_progress:
  43. cur.execute("RENAME TABLE reader_pro_progress TO user_progress")
  44. # Legacy migration: reader_pro_config -> user_config
  45. cur.execute("SHOW TABLES LIKE 'reader_pro_config'")
  46. has_legacy_config = bool(cur.fetchone())
  47. cur.execute("SHOW TABLES LIKE 'user_config'")
  48. has_user_config = bool(cur.fetchone())
  49. if has_legacy_config and not has_user_config:
  50. cur.execute("RENAME TABLE reader_pro_config TO user_config")
  51. cur.execute(
  52. """
  53. CREATE TABLE IF NOT EXISTS user (
  54. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  55. username VARCHAR(64) NOT NULL UNIQUE,
  56. password_hash VARCHAR(255) NOT NULL,
  57. is_admin TINYINT(1) NOT NULL DEFAULT 0,
  58. is_active TINYINT(1) NOT NULL DEFAULT 1,
  59. session_token VARCHAR(128) NULL,
  60. session_expires_at DATETIME NULL,
  61. last_file VARCHAR(1024) NULL,
  62. last_page INT NULL,
  63. created_at DATETIME NOT NULL,
  64. updated_at DATETIME NOT NULL
  65. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  66. """
  67. )
  68. cur.execute("SHOW COLUMNS FROM user LIKE 'is_active'")
  69. if not cur.fetchone():
  70. cur.execute("ALTER TABLE user ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER is_admin")
  71. cur.execute(
  72. """
  73. CREATE TABLE IF NOT EXISTS user_progress (
  74. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  75. user_id BIGINT NOT NULL,
  76. file_path VARCHAR(512) NOT NULL,
  77. page INT NOT NULL,
  78. updated_at DATETIME NOT NULL,
  79. UNIQUE KEY uniq_user_file (user_id, file_path),
  80. KEY idx_user_updated (user_id, updated_at),
  81. CONSTRAINT fk_user_progress_user
  82. FOREIGN KEY (user_id) REFERENCES user(id)
  83. ON DELETE CASCADE
  84. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  85. """
  86. )
  87. cur.execute(
  88. """
  89. CREATE TABLE IF NOT EXISTS user_config (
  90. config_key VARCHAR(128) PRIMARY KEY,
  91. config_value TEXT NULL,
  92. updated_at DATETIME NOT NULL
  93. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  94. """
  95. )
  96. now = datetime.now(timezone.utc).replace(tzinfo=None)
  97. admin_hash = hash_password("admin123")
  98. cur.execute("SELECT id FROM user WHERE username=%s", ("admin",))
  99. row = cur.fetchone()
  100. if row:
  101. cur.execute(
  102. """
  103. UPDATE user
  104. SET password_hash=%s, is_admin=1, is_active=1, updated_at=%s
  105. WHERE username=%s
  106. """,
  107. (admin_hash, now, "admin"),
  108. )
  109. admin_action = "updated"
  110. else:
  111. cur.execute(
  112. """
  113. INSERT INTO user (username, password_hash, is_admin, is_active, created_at, updated_at)
  114. VALUES (%s, %s, 1, 1, %s, %s)
  115. """,
  116. ("admin", admin_hash, now, now),
  117. )
  118. admin_action = "created"
  119. finally:
  120. conn.close()
  121. return admin_action
  122. def main():
  123. ensure_database()
  124. admin_action = ensure_tables_and_admin()
  125. print(f"[OK] Database `{MYSQL_DATABASE}` initialized.")
  126. print("[OK] Tables: user, user_progress, user_config")
  127. print(f"[OK] Admin user `admin` {admin_action}, password set to `admin123`")
  128. if __name__ == "__main__":
  129. main()