mirror of
https://github.com/wlinator/luminara.git
synced 2024-10-02 22:23:13 +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
|
from loguru import logger
|
||||||
import asyncio
|
import asyncio
|
||||||
from loader import CogLoader
|
from loader import CogLoader
|
||||||
|
from db.database import run_migrations
|
||||||
|
|
||||||
|
|
||||||
class Luminara(commands.Bot):
|
class Luminara(commands.Bot):
|
||||||
|
@ -14,13 +15,15 @@ class Luminara(commands.Bot):
|
||||||
|
|
||||||
async def setup(self) -> None:
|
async def setup(self) -> None:
|
||||||
try:
|
try:
|
||||||
pass
|
run_migrations()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to setup: {e}")
|
logger.error(f"Failed to setup: {e}")
|
||||||
|
await self.shutdown()
|
||||||
|
|
||||||
await self.load_cogs()
|
await self.load_cogs()
|
||||||
|
|
||||||
async def load_cogs(self) -> None:
|
async def load_cogs(self) -> None:
|
||||||
logger.info("Loading cogs...")
|
logger.debug("Loading cogs...")
|
||||||
await CogLoader.setup(bot=self)
|
await CogLoader.setup(bot=self)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@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