mirror of
https://github.com/wlinator/luminara.git
synced 2024-10-02 20:23:12 +00:00
Add database and migrations from v2
This commit is contained in:
parent
fc2d06a944
commit
0b3c05ddc9
5 changed files with 294 additions and 2 deletions
|
@ -2,6 +2,7 @@ from discord.ext import commands
|
|||
from loguru import logger
|
||||
import asyncio
|
||||
from loader import CogLoader
|
||||
from db.database import run_migrations
|
||||
|
||||
|
||||
class Luminara(commands.Bot):
|
||||
|
@ -14,13 +15,15 @@ class Luminara(commands.Bot):
|
|||
|
||||
async def setup(self) -> None:
|
||||
try:
|
||||
pass
|
||||
run_migrations()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to setup: {e}")
|
||||
await self.shutdown()
|
||||
|
||||
await self.load_cogs()
|
||||
|
||||
async def load_cogs(self) -> None:
|
||||
logger.info("Loading cogs...")
|
||||
logger.debug("Loading cogs...")
|
||||
await CogLoader.setup(bot=self)
|
||||
|
||||
@commands.Cog.listener()
|
||||
|
|
115
db/database.py
Normal file
115
db/database.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
import os
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
import mysql.connector
|
||||
from loguru import logger
|
||||
from mysql.connector import pooling
|
||||
|
||||
from lib.const import CONST
|
||||
|
||||
|
||||
def create_connection_pool(name: str, size: int) -> pooling.MySQLConnectionPool:
|
||||
return pooling.MySQLConnectionPool(
|
||||
pool_name=name,
|
||||
pool_size=size,
|
||||
host="db",
|
||||
port=3306,
|
||||
database=CONST.MARIADB_DATABASE,
|
||||
user=CONST.MARIADB_USER,
|
||||
password=CONST.MARIADB_PASSWORD,
|
||||
charset="utf8mb4",
|
||||
collation="utf8mb4_unicode_ci",
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
_cnxpool = create_connection_pool("core-pool", 25)
|
||||
except mysql.connector.Error as e:
|
||||
logger.critical(f"Couldn't create the MySQL connection pool: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
def execute_query(query, values=None):
|
||||
with _cnxpool.get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(query, values)
|
||||
conn.commit()
|
||||
return cursor
|
||||
|
||||
|
||||
def select_query(query, values=None):
|
||||
with _cnxpool.get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(query, values)
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
def select_query_one(query, values=None):
|
||||
with _cnxpool.get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(query, values)
|
||||
output = cursor.fetchone()
|
||||
return output[0] if output else None
|
||||
|
||||
|
||||
def select_query_dict(query, values=None):
|
||||
with _cnxpool.get_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
cursor.execute(query, values)
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
def run_migrations():
|
||||
migrations_dir = "db/migrations"
|
||||
migration_files = sorted(
|
||||
[f for f in os.listdir(migrations_dir) if f.endswith(".sql")],
|
||||
)
|
||||
|
||||
with _cnxpool.get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
# Create migrations table if it doesn't exist
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
|
||||
for migration_file in migration_files:
|
||||
# Check if migration has already been applied
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM migrations WHERE filename = %s",
|
||||
(migration_file,),
|
||||
)
|
||||
if cursor.fetchone()[0] > 0:
|
||||
logger.debug(
|
||||
f"Migration {migration_file} already applied, skipping.",
|
||||
)
|
||||
continue
|
||||
|
||||
# Read and execute migration file
|
||||
migration_sql = pathlib.Path(
|
||||
os.path.join(migrations_dir, migration_file),
|
||||
).read_text()
|
||||
try:
|
||||
# Split the migration file into individual statements
|
||||
statements = re.split(r";\s*$", migration_sql, flags=re.MULTILINE)
|
||||
for statement in statements:
|
||||
if statement.strip():
|
||||
cursor.execute(statement)
|
||||
|
||||
# Record successful migration
|
||||
cursor.execute(
|
||||
"INSERT INTO migrations (filename) VALUES (%s)",
|
||||
(migration_file,),
|
||||
)
|
||||
conn.commit()
|
||||
logger.debug(f"Successfully applied migration: {migration_file}")
|
||||
except mysql.connector.Error as e:
|
||||
conn.rollback()
|
||||
logger.error(f"Error applying migration {migration_file}: {e}")
|
||||
raise
|
||||
|
||||
logger.success("All database migrations completed.")
|
109
db/migrations/v2_5_8_init.sql
Normal file
109
db/migrations/v2_5_8_init.sql
Normal file
|
@ -0,0 +1,109 @@
|
|||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS xp (
|
||||
user_id BIGINT NOT NULL,
|
||||
guild_id BIGINT NOT NULL,
|
||||
user_xp INT NOT NULL,
|
||||
user_level INT NOT NULL,
|
||||
cooldown DECIMAL(15,2),
|
||||
PRIMARY KEY (user_id, guild_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS currency (
|
||||
user_id BIGINT NOT NULL,
|
||||
balance BIGINT NOT NULL,
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blackjack (
|
||||
id INT AUTO_INCREMENT,
|
||||
user_id BIGINT,
|
||||
is_won BOOLEAN,
|
||||
bet BIGINT,
|
||||
payout BIGINT,
|
||||
hand_player TEXT,
|
||||
hand_dealer TEXT,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS slots (
|
||||
id INT AUTO_INCREMENT,
|
||||
user_id BIGINT,
|
||||
is_won BOOLEAN,
|
||||
bet BIGINT,
|
||||
payout BIGINT,
|
||||
spin_type TEXT,
|
||||
icons TEXT,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dailies (
|
||||
id INT AUTO_INCREMENT,
|
||||
user_id BIGINT,
|
||||
amount BIGINT,
|
||||
claimed_at TINYTEXT,
|
||||
streak INT,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS item (
|
||||
id INT AUTO_INCREMENT,
|
||||
name TEXT,
|
||||
display_name TEXT,
|
||||
description TEXT,
|
||||
image_url TEXT,
|
||||
emote_id BIGINT,
|
||||
quote TEXT,
|
||||
type TEXT,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inventory (
|
||||
user_id BIGINT,
|
||||
item_id INT,
|
||||
quantity INT,
|
||||
|
||||
PRIMARY KEY (user_id, item_id),
|
||||
FOREIGN KEY (item_id) REFERENCES item (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS birthdays (
|
||||
user_id BIGINT NOT NULL,
|
||||
guild_id BIGINT NOT NULL,
|
||||
birthday DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (user_id, guild_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS guild_config (
|
||||
guild_id BIGINT NOT NULL,
|
||||
prefix TINYTEXT,
|
||||
birthday_channel_id BIGINT,
|
||||
command_channel_id BIGINT, /* NULL: users can do XP & Currency commands everywhere. */
|
||||
intro_channel_id BIGINT,
|
||||
welcome_channel_id BIGINT,
|
||||
welcome_message TEXT,
|
||||
boost_channel_id BIGINT,
|
||||
boost_message TEXT,
|
||||
boost_image_url TEXT,
|
||||
level_channel_id BIGINT, /* level-up messages, if NULL the level-up message will be shown in current msg channel*/
|
||||
level_message TEXT, /* if NOT NULL and LEVEL_TYPE = 2, this can be a custom level up message. */
|
||||
level_message_type TINYINT(1) NOT NULL DEFAULT 1, /* 0: no level up messages, 1: levels.en-US.json, 2: generic message */
|
||||
PRIMARY KEY (guild_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS level_rewards (
|
||||
guild_id BIGINT NOT NULL,
|
||||
level INT NOT NULL,
|
||||
role_id BIGINT,
|
||||
persistent BOOLEAN,
|
||||
|
||||
PRIMARY KEY (guild_id, level)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blacklist_user (
|
||||
user_id BIGINT NOT NULL,
|
||||
reason TEXT,
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
21
db/migrations/v2_5_9_reactions.sql
Normal file
21
db/migrations/v2_5_9_reactions.sql
Normal file
|
@ -0,0 +1,21 @@
|
|||
-- Create a table to store custom reactions
|
||||
CREATE TABLE IF NOT EXISTS custom_reactions (
|
||||
id SERIAL PRIMARY KEY, -- Unique identifier for each custom reaction
|
||||
trigger_text TEXT NOT NULL, -- The text that triggers the custom reaction
|
||||
response TEXT, -- The response text for the custom reaction (nullable for emoji reactions)
|
||||
emoji_id BIGINT UNSIGNED, -- The emoji for the custom reaction (nullable for text responses)
|
||||
is_emoji BOOLEAN DEFAULT FALSE, -- Indicates if the reaction is a discord emoji reaction
|
||||
is_full_match BOOLEAN DEFAULT FALSE, -- Indicates if the trigger matches the full content of the message
|
||||
is_global BOOLEAN DEFAULT TRUE, -- Indicates if the reaction is global or specific to a guild
|
||||
guild_id BIGINT UNSIGNED, -- The ID of the guild where the custom reaction is used (nullable for global reactions)
|
||||
creator_id BIGINT UNSIGNED NOT NULL, -- The ID of the user who created the custom reaction
|
||||
usage_count INT DEFAULT 0, -- The number of times a custom reaction has been used
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the custom reaction was created
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the custom reaction was last updated
|
||||
CONSTRAINT unique_trigger_guild UNIQUE (trigger_text, guild_id) -- Ensure that the combination of trigger_text, guild_id, and is_full_match is unique
|
||||
);
|
||||
|
||||
-- Create indexes to speed up lookups
|
||||
CREATE OR REPLACE INDEX idx_custom_reactions_guild_id ON custom_reactions(guild_id);
|
||||
CREATE OR REPLACE INDEX idx_custom_reactions_creator_id ON custom_reactions(creator_id);
|
||||
CREATE OR REPLACE INDEX idx_custom_reactions_trigger_text ON custom_reactions(trigger_text);
|
44
db/migrations/v2_6_0_cases.sql
Normal file
44
db/migrations/v2_6_0_cases.sql
Normal file
|
@ -0,0 +1,44 @@
|
|||
CREATE TABLE IF NOT EXISTS mod_log (
|
||||
guild_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||
channel_id BIGINT UNSIGNED NOT NULL,
|
||||
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cases (
|
||||
id SERIAL PRIMARY KEY,
|
||||
guild_id BIGINT UNSIGNED NOT NULL,
|
||||
case_number INT UNSIGNED NOT NULL,
|
||||
target_id BIGINT UNSIGNED NOT NULL,
|
||||
moderator_id BIGINT UNSIGNED NOT NULL,
|
||||
action_type ENUM(
|
||||
'WARN',
|
||||
'TIMEOUT',
|
||||
'UNTIMEOUT',
|
||||
'KICK',
|
||||
'BAN',
|
||||
'UNBAN',
|
||||
'SOFTBAN',
|
||||
'TEMPBAN',
|
||||
'NOTE',
|
||||
'MUTE',
|
||||
'UNMUTE',
|
||||
'DEAFEN',
|
||||
'UNDEAFEN'
|
||||
) NOT NULL,
|
||||
reason TEXT,
|
||||
duration INT UNSIGNED, -- for timeouts
|
||||
expires_at TIMESTAMP, -- for tempbans & mutes
|
||||
modlog_message_id BIGINT UNSIGNED,
|
||||
is_closed BOOLEAN NOT NULL DEFAULT FALSE, -- to indicate if the case is closed
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_case (guild_id, case_number)
|
||||
);
|
||||
|
||||
|
||||
CREATE OR REPLACE INDEX idx_cases_guild_id ON cases(guild_id);
|
||||
CREATE OR REPLACE INDEX idx_cases_target_id ON cases(target_id);
|
||||
CREATE OR REPLACE INDEX idx_cases_moderator_id ON cases(moderator_id);
|
||||
CREATE OR REPLACE INDEX idx_cases_action_type ON cases(action_type);
|
Loading…
Reference in a new issue