diff --git a/bot/src/bot_data/abc/scheduled_event_repository_abc.py b/bot/src/bot_data/abc/scheduled_event_repository_abc.py new file mode 100644 index 00000000..ff0d54c2 --- /dev/null +++ b/bot/src/bot_data/abc/scheduled_event_repository_abc.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod + +from cpl_query.extension import List + +from bot_data.model.scheduled_event import ScheduledEvent + + +class ScheduledEventRepositoryABC(ABC): + @abstractmethod + def __init__(self): + pass + + @abstractmethod + def get_scheduled_events(self) -> List[ScheduledEvent]: + pass + + @abstractmethod + def get_scheduled_event_by_id(self, id: int) -> ScheduledEvent: + pass + + @abstractmethod + def get_scheduled_events_by_server_id(self, id: int) -> List[ScheduledEvent]: + pass + + @abstractmethod + def add_scheduled_event(self, scheduled_event: ScheduledEvent): + pass + + @abstractmethod + def update_scheduled_event(self, scheduled_event: ScheduledEvent): + pass + + @abstractmethod + def delete_scheduled_event(self, scheduled_event: ScheduledEvent): + pass diff --git a/bot/src/bot_data/migration/db_history_scripts/scheduled_events.sql b/bot/src/bot_data/migration/db_history_scripts/scheduled_events.sql new file mode 100644 index 00000000..5ba2cf39 --- /dev/null +++ b/bot/src/bot_data/migration/db_history_scripts/scheduled_events.sql @@ -0,0 +1,65 @@ +CREATE TABLE IF NOT EXISTS `ScheduledEventsHistory` +( + `Id` BIGINT(20) NOT NULL, + `Interval` VARCHAR(255) NOT NULL, + `Name` VARCHAR(255) NOT NULL, + `Description` VARCHAR(255) NOT NULL, + `ChannelId` BIGINT NULL, + `StartTime` DATETIME(6) NOT NULL, + `EndTime` DATETIME(6) NULL, + `EntityType` ENUM (1,2,3) NOT NULL, + `Location` VARCHAR(255) NULL, + `ServerId` BIGINT(20) DEFAULT NULL, + `Deleted` BOOL DEFAULT FALSE, + `DateFrom` DATETIME(6) NOT NULL, + `DateTo` DATETIME(6) NOT NULL +); + +DROP TRIGGER IF EXISTS `TR_ScheduledEventsUpdate`; + +CREATE TRIGGER `TR_ScheduledEventsUpdate` + AFTER UPDATE + ON `ScheduledEvents` + FOR EACH ROW +BEGIN + INSERT INTO `ScheduledEventsHistory` (`Id`, + `Interval`, + `Name`, + `Description`, + `ChannelId`, + `StartTime`, + `EndTime`, + `EntityType`, + `Location`, + `ServerId`, + `DateFrom`, + `DateTo`) + VALUES (OLD.Id, OLD.Interval, OLD.Name, OLD.Description, OLD.ChannelId, OLD.StartTime, OLD.EndTime, OLD.EntityType, + OLD.Location, OLD.ServerId, OLD.LastModifiedAt, + CURRENT_TIMESTAMP(6)); +END; + +DROP TRIGGER IF EXISTS `TR_ScheduledEventsDelete`; + +CREATE TRIGGER `TR_ScheduledEventsDelete` + AFTER DELETE + ON `ScheduledEvents` + FOR EACH ROW +BEGIN + INSERT INTO `ScheduledEventsHistory` (`Id`, + `Interval`, + `Name`, + `Description`, + `ChannelId`, + `StartTime`, + `EndTime`, + `EntityType`, + `Location`, + `ServerId`, + `Deleted`, + `DateFrom`, + `DateTo`) + VALUES (OLD.Id, OLD.Interval, OLD.Name, OLD.Description, OLD.ChannelId, OLD.StartTime, OLD.EndTime, OLD.EntityType, + OLD.Location, OLD.ServerId, TRUE, OLD.LastModifiedAt, + CURRENT_TIMESTAMP(6)); +END; \ No newline at end of file diff --git a/bot/src/bot_data/migration/scheduled_event_migration.py b/bot/src/bot_data/migration/scheduled_event_migration.py new file mode 100644 index 00000000..50cdcec1 --- /dev/null +++ b/bot/src/bot_data/migration/scheduled_event_migration.py @@ -0,0 +1,45 @@ +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.migration_abc import MigrationABC +from bot_data.db_context import DBContext + + +class ScheduledEventMigration(MigrationABC): + name = "1.2.0_ScheduledEventMigration" + + def __init__(self, logger: DatabaseLogger, db: DBContext): + MigrationABC.__init__(self) + self._logger = logger + self._db = db + self._cursor = db.cursor + + def upgrade(self): + self._logger.debug(__name__, "Running upgrade") + + self._cursor.execute( + str( + f""" + CREATE TABLE IF NOT EXISTS `ScheduledEvents` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `Interval` VARCHAR(255) NOT NULL, + `Name` VARCHAR(255) NOT NULL, + `Description` VARCHAR(255) NOT NULL, + `ChannelId` BIGINT NULL, + `StartTime` DATETIME(6) NOT NULL, + `EndTime` DATETIME(6) NULL, + `EntityType` ENUM(1,2,3) NOT NULL, + `Location` VARCHAR(255) NULL, + `ServerId` BIGINT, + `CreatedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6), + `LastModifiedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY(`Id`), + FOREIGN KEY (`ServerId`) REFERENCES `Servers`(`ServerId`) + ); + """ + ) + ) + + self._exec(__file__, "scheduled_events.sql") + + def downgrade(self): + self._cursor.execute("DROP TABLE `ShortRoleNames`;") + self._cursor.execute("DROP TABLE `ShortRoleNamesHistory`;") diff --git a/bot/src/bot_data/model/scheduled_event.py b/bot/src/bot_data/model/scheduled_event.py new file mode 100644 index 00000000..fa05c946 --- /dev/null +++ b/bot/src/bot_data/model/scheduled_event.py @@ -0,0 +1,187 @@ +from datetime import datetime +from typing import Optional + +import discord +from cpl_core.database import TableABC + +from bot_data.model.server import Server + + +class ScheduledEvent(TableABC): + def __init__( + self, + interval: str, + name: str, + description: str, + channel_id: int, + start_time: datetime, + end_time: Optional[datetime], + entity_type: discord.EntityType, + location: Optional[str], + server: Optional[Server], + created_at: datetime = None, + modified_at: datetime = None, + id=0, + ): + self._id = id + self._interval = interval + self._name = name + self._description = description + self._channel_id = channel_id + self._start_time = start_time + self._end_time = end_time + self._entity_type = entity_type + self._location = location + self._server = server + + TableABC.__init__(self) + self._created_at = created_at if created_at is not None else self._created_at + self._modified_at = modified_at if modified_at is not None else self._modified_at + + @property + def id(self) -> int: + return self._id + + @property + def interval(self) -> str: + return self._interval + + @interval.setter + def interval(self, value: str): + self._interval = value + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = value + + @property + def description(self) -> str: + return self._description + + @description.setter + def description(self, value: str): + self._description = value + + @property + def channel_id(self) -> int: + return self._channel_id + + @channel_id.setter + def channel_id(self, value: int): + self._channel_id = value + + @property + def start_time(self) -> datetime: + return self._start_time + + @start_time.setter + def start_time(self, value: datetime): + self._start_time = value + + @property + def end_time(self) -> datetime: + return self._end_time + + @end_time.setter + def end_time(self, value: datetime): + self._end_time = value + + @property + def entity_type(self) -> discord.EntityType: + return self._entity_type + + @entity_type.setter + def entity_type(self, value: discord.EntityType): + self._entity_type = value + + @property + def location(self) -> str: + return self._location + + @location.setter + def location(self, value: str): + self._location = value + + @property + def server(self) -> Server: + return self._server + + @server.setter + def server(self, value: Server): + self._server = value + + @staticmethod + def get_select_all_string() -> str: + return str( + f""" + SELECT * FROM `ScheduledEvents`; + """ + ) + + @staticmethod + def get_select_by_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `ScheduledEvents` + WHERE `Id` = {id}; + """ + ) + + @staticmethod + def get_select_by_server_id_string(s_id: int) -> str: + return str( + f""" + SELECT * FROM `ScheduledEvents` + WHERE `ServerId` = {s_id}; + """ + ) + + @property + def insert_string(self) -> str: + return str( + f""" + INSERT INTO `ScheduledEvents` ( + `Interval`, `Name`, `Description`, `ChannelId`, `StartTime`, `EndTime`, `EntityType`, `Location`, `ServerId`, + ) VALUES ( + '{self._interval}', + '{self._name}', + '{self._description}', + {"NULL" if self._channel_id is None else f"'{self._channel_id}'"}, + '{self._start_time}', + {"NULL" if self._end_time is None else f"'{self._end_time}'"}, + '{self._entity_type}', + {"NULL" if self._location is None else f"'{self._location}'"}, + {self._server.id} + ); + """ + ) + + @property + def udpate_string(self) -> str: + return str( + f""" + UPDATE `ScheduledEvents` + SET `Interval` = '{self._interval}', + `Name` = '{self._name}', + `Description` = '{self._start_time}', + `ChannelId` = {"NULL" if self._channel_id is None else f"'{self._channel_id}'"}, + `StartTime` = '{self._start_time}', + `EndTime` = {"NULL" if self._end_time is None else f"'{self._end_time}'"}, + `EntityType` = '{self._entity_type}', + `Location` = {"NULL" if self._location is None else f"'{self._location}'"} + WHERE `Id` = {self._id}; + """ + ) + + @property + def delete_string(self) -> str: + return str( + f""" + DELETE FROM `ScheduledEvents` + WHERE `Id` = {self._id}; + """ + ) diff --git a/bot/src/bot_data/model/scheduled_event_history.py b/bot/src/bot_data/model/scheduled_event_history.py new file mode 100644 index 00000000..b910e796 --- /dev/null +++ b/bot/src/bot_data/model/scheduled_event_history.py @@ -0,0 +1,118 @@ +from datetime import datetime +from typing import Optional + +import discord +from cpl_core.database import TableABC + +from bot_data.abc.history_table_abc import HistoryTableABC +from bot_data.model.server import Server + + +class ScheduledEventHistory(HistoryTableABC): + def __init__( + self, + interval: str, + name: str, + description: str, + channel_id: int, + start_time: datetime, + end_time: Optional[datetime], + entity_type: discord.EntityType, + location: Optional[str], + server: Optional[Server], + deleted: bool, + date_from: str, + date_to: str, + id=0, + ): + HistoryTableABC.__init__(self) + self._id = id + self._interval = interval + self._name = name + self._description = description + self._channel_id = channel_id + self._start_time = start_time + self._end_time = end_time + self._entity_type = entity_type + self._location = location + self._server = server + + self._deleted = deleted + self._date_from = date_from + self._date_to = date_to + + @property + def id(self) -> int: + return self._id + + @property + def interval(self) -> str: + return self._interval + + @interval.setter + def interval(self, value: str): + self._interval = value + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = value + + @property + def description(self) -> str: + return self._description + + @description.setter + def description(self, value: str): + self._description = value + + @property + def channel_id(self) -> int: + return self._channel_id + + @channel_id.setter + def channel_id(self, value: int): + self._channel_id = value + + @property + def start_time(self) -> datetime: + return self._start_time + + @start_time.setter + def start_time(self, value: datetime): + self._start_time = value + + @property + def end_time(self) -> datetime: + return self._end_time + + @end_time.setter + def end_time(self, value: datetime): + self._end_time = value + + @property + def entity_type(self) -> discord.EntityType: + return self._entity_type + + @entity_type.setter + def entity_type(self, value: discord.EntityType): + self._entity_type = value + + @property + def location(self) -> str: + return self._location + + @location.setter + def location(self, value: str): + self._location = value + + @property + def server(self) -> Server: + return self._server + + @server.setter + def server(self, value: Server): + self._server = value diff --git a/bot/src/bot_data/service/scheduled_event_repository_service.py b/bot/src/bot_data/service/scheduled_event_repository_service.py new file mode 100644 index 00000000..7d5e67ba --- /dev/null +++ b/bot/src/bot_data/service/scheduled_event_repository_service.py @@ -0,0 +1,89 @@ +from typing import Optional + +from cpl_core.database.context import DatabaseContextABC +from cpl_query.extension import List + +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC +from bot_data.model.scheduled_event import ScheduledEvent + + +class ScheduledEventRepositoryService(ScheduledEventRepositoryABC): + def __init__( + self, + logger: DatabaseLogger, + db_context: DatabaseContextABC, + servers: ServerRepositoryABC, + ): + self._logger = logger + self._context = db_context + + self._servers = servers + + ScheduledEventRepositoryABC.__init__(self) + + @staticmethod + def _get_value_from_result(value: any) -> Optional[any]: + if isinstance(value, str) and "NULL" in value: + return None + + return value + + def _scheduled_event_from_result(self, sql_result: tuple) -> ScheduledEvent: + return ScheduledEvent( + self._get_value_from_result(sql_result[0]), # interval + self._get_value_from_result(sql_result[1]), # name + self._get_value_from_result(sql_result[2]), # description + int(self._get_value_from_result(sql_result[3])), # channel_id + self._get_value_from_result(sql_result[4]), # start_time + self._get_value_from_result(sql_result[5]), # end_time + self._get_value_from_result(sql_result[6]), # entity_type + self._get_value_from_result(sql_result[7]), # location + self._servers.get_server_by_id((sql_result[8])), # server + self._get_value_from_result(sql_result[9]), # created_at + self._get_value_from_result(sql_result[10]), # modified_at + id=self._get_value_from_result(sql_result[0]), + ) + + def get_scheduled_events(self) -> List[ScheduledEvent]: + scheduled_events = List(ScheduledEvent) + self._logger.trace(__name__, f"Send SQL command: {ScheduledEvent.get_select_all_string()}") + results = self._context.select(ScheduledEvent.get_select_all_string()) + for result in results: + self._logger.trace(__name__, f"Get scheduled_event with id {result[0]}") + scheduled_events.append(self._scheduled_event_from_result(result)) + + return scheduled_events + + def get_scheduled_event_by_id(self, id: int) -> ScheduledEvent: + self._logger.trace(__name__, f"Send SQL command: {ScheduledEvent.get_select_by_id_string(id)}") + result = self._context.select(ScheduledEvent.get_select_by_id_string(id))[0] + + return self._scheduled_event_from_result(result) + + def get_scheduled_events_by_server_id(self, server_id: int) -> List[ScheduledEvent]: + scheduled_events = List(ScheduledEvent) + self._logger.trace( + __name__, + f"Send SQL command: {ScheduledEvent.get_select_by_server_id_string(server_id)}", + ) + results = self._context.select(ScheduledEvent.get_select_by_server_id_string(server_id)) + + for result in results: + self._logger.trace(__name__, f"Get scheduled_event with id {result[0]}") + scheduled_events.append(self._scheduled_event_from_result(result)) + + return scheduled_events + + def add_scheduled_event(self, scheduled_event: ScheduledEvent): + self._logger.trace(__name__, f"Send SQL command: {scheduled_event.insert_string}") + self._context.cursor.execute(scheduled_event.insert_string) + + def update_scheduled_event(self, scheduled_event: ScheduledEvent): + self._logger.trace(__name__, f"Send SQL command: {scheduled_event.udpate_string}") + self._context.cursor.execute(scheduled_event.udpate_string) + + def delete_scheduled_event(self, scheduled_event: ScheduledEvent): + self._logger.trace(__name__, f"Send SQL command: {scheduled_event.delete_string}") + self._context.cursor.execute(scheduled_event.delete_string)