1
Fork 0
mirror of https://github.com/wlinator/luminara.git synced 2024-10-02 18:03:12 +00:00

Add database and migrations from v2

This commit is contained in:
wlinator 2024-08-28 10:44:07 -04:00
parent fc2d06a944
commit 0b3c05ddc9
5 changed files with 294 additions and 2 deletions

View file

@ -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
View 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.")

View 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)
);

View 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);

View 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);