"""File holds all model definitions of SQL tables.""" from sqlalchemy.dialects.mysql import BIGINT, BOOLEAN, INTEGER, VARCHAR from sqlmodel import Column, Field, ForeignKey, Relationship, SQLModel class ElectionInformation(SQLModel, table=True): """ Represents all required information for an election. Attributes: name: Name of the election. thread_id: Thread of where all the election related messages take place. council_vote_msg_id: Message where the council vote is presented. high_council_vote_msg_id: Message where the high council vote is presented. council_nomination_msg_id: Message where council nomination button is held. high_council_nomination_msg_id: Message where high council nomination button is held. council_members: List of council members who ran during this election. votes: All votes cast during this election. """ __tablename__: str = "ElectionInformation" name: str = Field(sa_column=Column("ElectionName", VARCHAR, primary_key=True, unique=True), max_length=30) thread_id: int = Field(sa_column=Column("ThreadId", BIGINT(unsigned=True), nullable=False)) council_vote_msg_id: int | None = Field( sa_column=Column("CouncilVoteMsgId", BIGINT(unsigned=True), nullable=True), default=None ) high_council_vote_msg_id: int | None = Field( sa_column=Column("HighCouncilVoteMsgId", BIGINT(unsigned=True), nullable=True), default=None ) council_nomination_msg_id: int | None = Field( sa_column=Column("CouncilNominationMsgId", BIGINT(unsigned=True), nullable=True), default=None ) high_council_nomination_msg_id: int | None = Field( sa_column=Column("HighCouncilNominationMsgId", BIGINT(unsigned=True), nullable=True), default=None ) council_members: list["CouncilMember"] = Relationship(back_populates="election", cascade_delete=True) votes: list["Vote"] = Relationship(back_populates="election", cascade_delete=True) class CouncilMember(SQLModel, table=True): """ Represents members involved in a given election. Records speech message ids for general election and high council position. Booleans record if the user was elected to a council/high council position. Attributes: member_id: User that is running for council's discord id. speech_msg_id: Message id of user's speech. elected: Whether the user got elected to council. discord_manager: Whether the user got elected to the Discord Manager position. clan_leader: Whether the user got elected to the Clan Leader position. discord_manager_speech_id: Message id of the user's Discord Manager speech. clan_leader_speech_id: Message id of the user's Clan Leader speech. election: The election this user object is for. votes: A list of votes for this user. """ __tablename__: str = "CouncilMembers" election_name: str = Field( sa_column=Column( "ElectionName", VARCHAR, ForeignKey("ElectionInformation.ElectionName", ondelete="CASCADE"), primary_key=True, ), max_length=30, ) member_id: int = Field(sa_column=Column("MemberId", BIGINT(unsigned=True), primary_key=True, nullable=False)) speech_msg_id: int | None = Field( sa_column=Column("SpeechMsgId", BIGINT(unsigned=True), nullable=False), default=None ) elected: bool = Field(sa_column=Column("Elected", BOOLEAN), default=False) discord_manager: bool = Field(sa_column=Column("DiscordManager", BOOLEAN), default=False) clan_leader: bool = Field(sa_column=Column("ClanLeader", BOOLEAN), default=False) discord_manager_speech_id: int | None = Field( sa_column=Column("DiscordManagerSpeechId", BIGINT(unsigned=True), nullable=True), default=None ) clan_leader_speech_id: int | None = Field( sa_column=Column("ClanLeaderSpeechId", BIGINT(unsigned=True), nullable=True), default=None ) election: ElectionInformation | None = Relationship(back_populates="council_members") votes: list["Vote"] = Relationship(back_populates="council_member") class Vote(SQLModel, table=True): """ Represents a vote cast during an election. Attributes: election_name: Election this vote was cast in. voter_id: User id that cast the vote. voted_for_id: User id that was voted for. type: Type of election, council, discordmanager, or clanleader. election: A link to the ElectionInformation object that the vote was cast in. council_member: A link to the CouncilMember object that the vote is for. """ __tablename__: str = "Votes" election_name: str = Field( sa_column=Column( "ElectionName", VARCHAR, ForeignKey("ElectionInformation.ElectionName", ondelete="CASCADE"), primary_key=True, ), max_length=30, ) voter_id: int = Field(sa_column=Column("VoterId", BIGINT(unsigned=True), primary_key=True, nullable=False)) # might have to set this foreign key later with alembic migration... voted_for_id: int | None = Field( sa_column=Column("VotedForId", BIGINT(unsigned=True), ForeignKey("CouncilMembers.MemberId"), nullable=True) ) type: str = Field( sa_column=Column("Type", VARCHAR, primary_key=True, nullable=False, default="council"), max_length=14 ) election: ElectionInformation | None = Relationship(back_populates="votes") council_member: CouncilMember | None = Relationship(back_populates="votes") class UserBirthday(SQLModel, table=True): """ Represents a user's birth month and day. Attributes: id: User's discord id. birthday: Month/day that is a user's birthday. """ id: int = Field(sa_column=Column("UserId", BIGINT(unsigned=True), primary_key=True, nullable=False)) # TODO(imcompiling): split into month and day. Also relies on Alembic migration. # https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/93 birthday: str = Field(sa_column=Column("Birthday", VARCHAR, nullable=False), max_length=5) class Presence(SQLModel, table=True): """ Stores presences for the bot to pull and use. Attributes: id: Auto-incremented id for each entry. presence: The presence itself. """ __tablename__: str = "Presences" id: int | None = Field(sa_column=Column("Id", INTEGER, primary_key=True), default=None) presence: str = Field(sa_column=Column("Presence", VARCHAR, nullable=False), max_length=100) class Points(SQLModel, table=True): """ Stores activity points a user can gain by participating in clan activities. Event points and clan points were separated out. Attributes: quarter_name: Name of the quarter the points were earned in. user_id: The user that earned them. Is a discord id if the user is a discord member, otherwise their rsn. discord_member: Boolean on whether the user is a discord member or not. Used to know what the user_id represents. event_points: Event points earned during the quarter. clan_points: Clan points earned during the quarter. """ quarter_name: str = Field(sa_column=Column("QuarterName", VARCHAR, primary_key=True, nullable=False), max_length=30) user_id: str = Field(sa_column=Column("UserId", VARCHAR, primary_key=True, nullable=False), max_length=32) discord_member: bool = Field(sa_column=Column("DiscordMember", BOOLEAN, nullable=False)) event_points: int = Field(sa_column=Column("EventPoints", INTEGER(unsigned=True), nullable=False), default=0) clan_points: int = Field(sa_column=Column("ClanPoints", INTEGER(unsigned=True), nullable=False), default=0) class Message(SQLModel, table=True): """ Table is used to map folders and files to channels and messages in discord. If folder name is stored in path, the corresponding id is a channel id. If the path is to a file, the id is for a message. Attributes: path: Either directory or file pathname. id: Channel or message id depending on the path. """ __tablename__: str = "Messages" path: str = Field(sa_column=Column("Path", VARCHAR, primary_key=True, nullable=False), max_length=100) id: int = Field(sa_column=Column("Id", BIGINT(unsigned=True), nullable=False)) class PointsLimit(SQLModel, table=True): """ Table tracks how many points a user has gained in a week. Council didn't want users spamming points, so they added a limit to how many can be gained in a week. This table tracks point gain per week. Attributes: user_id: The user that earned points. Is a discord id if the user is a discord member, otherwise their rsn. week: week number/year representation of the week. Ex: 04/2025. event_points: Number of event points earned during this week. clan_points: Number of clan points earned during this week. """ user_id: str = Field(sa_column=Column("UserId", VARCHAR, primary_key=True, nullable=False), max_length=32) # Maybe should break this out into two columns, week and year. Better data validation and more clear. week: str = Field(sa_column=Column("Week", VARCHAR, primary_key=True, nullable=False), max_length=7) event_points: int = Field(sa_column=Column("EventPoints", INTEGER(unsigned=True), nullable=False), default=0) clan_points: int = Field(sa_column=Column("ClanPoints", INTEGER(unsigned=True), nullable=False), default=0)