Skip to content

Commit 14ef06a

Browse files
authored
Merge pull request #14 from ae-utbm/peewee
Setup database
2 parents 5e85439 + f9e833e commit 14ef06a

8 files changed

Lines changed: 97 additions & 65 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ cover/
6666
*.pot
6767

6868
local_settings.py
69+
*.sqlite
6970
*.sqlite3
7071
*.sqlite3-journal
7172

db/__init__.py

Whitespace-only changes.

db/models.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from datetime import UTC, datetime
2+
3+
import peewee
4+
from peewee import Model, SqliteDatabase
5+
6+
from src.settings import BASE_DIR
7+
8+
# SQLite may not be suited for production, but is
9+
# perfectly for the initial development phase
10+
db = SqliteDatabase(BASE_DIR / "db.sqlite3")
11+
12+
13+
class DbBaseModel(Model):
14+
created_at = peewee.DateTimeField(default=datetime.now(tz=UTC))
15+
updated_at = peewee.DateTimeField()
16+
17+
def save(self, *args, **kwargs):
18+
self.updated_at = datetime.now(tz=UTC)
19+
return super().save(*args, **kwargs)
20+
21+
class Meta:
22+
database = db
23+
24+
25+
class User(DbBaseModel):
26+
discord_id = peewee.IntegerField(primary_key=True)
27+
sith_id = peewee.IntegerField(null=True)
28+
username = peewee.CharField()
29+
30+
31+
class Club(DbBaseModel):
32+
name = peewee.CharField(max_length=64, unique=True)
33+
category_id = peewee.IntegerField(unique=True)
34+
sith_id = peewee.IntegerField(unique=True)
35+
president_role_id = peewee.IntegerField(unique=True)
36+
treasurer_role_id = peewee.IntegerField(unique=True)
37+
member_role_id = peewee.IntegerField(unique=True)
38+
former_member_role_id = peewee.IntegerField(unique=True)
39+
40+
41+
def init():
42+
db.create_tables([User, Club])

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ services:
22
discord:
33
build: .
44
volumes:
5-
- /data:/app/data
5+
- ./db.sqlite3:/app/db.sqlite3
66
- ./log:/app/log

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ requires-python = ">=3.12"
77
dependencies = [
88
"aiohttp>=3.13.0",
99
"discord-py[speed]>=2.6.4",
10+
"peewee>=3.18.2",
1011
"pydantic-settings>=2.11.0",
1112
"pydantic>=2.12.0",
1213
"pytz>=2025.2",

src/commands/club.py

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from discord.ext import commands
88
from discord.ext.commands import BadArgument
99

10+
from db.models import Club
1011
from src.client import ClubSchema # noqa TC001
11-
from src.services.club import ClubService, DiscordClub
12+
from src.services.club import ClubExists, ClubService
1213
from src.settings import Settings
1314

1415
if TYPE_CHECKING:
@@ -63,14 +64,14 @@ async def remove_club_member(
6364
member: Member,
6465
):
6566
await interaction.response.defer(thinking=True)
66-
discord_club = DiscordClub.load(club.id)
67-
role_membre = utils.get(member.guild.roles, id=discord_club.member_role_id)
68-
if not discord_club:
67+
db_club = Club.get_or_none(Club.sith_id == club.id)
68+
if not db_club:
6969
await interaction.followup.send(f"Le club : {club.name} n'existe pas")
7070
return
71+
role_membre = utils.get(member.guild.roles, id=db_club.member_role_id)
7172
if (
7273
not interaction.user.guild_permissions.manage_roles
73-
and not interaction.user.get_role(discord_club.president_role_id)
74+
and not interaction.user.get_role(db_club.president_role_id)
7475
):
7576
await interaction.followup.send(
7677
"Seul le président du club et les admins peuvent retirer un membre"
@@ -79,7 +80,7 @@ async def remove_club_member(
7980
if role_membre not in member.roles:
8081
await interaction.followup.send("Cet utilisateur n'est pas dans le club")
8182
return
82-
await self.club_service.remove_member(discord_club, member)
83+
await self.club_service.remove_member(db_club, member)
8384
await interaction.followup.send(
8485
f"{member.name} a été retiré du club :thumbs_up:"
8586
)
@@ -93,14 +94,14 @@ async def add_club_member(
9394
member: Member,
9495
):
9596
await interaction.response.defer(thinking=True)
96-
discord_club = DiscordClub.load(club.id)
97-
role_membre = utils.get(member.guild.roles, id=discord_club.member_role_id)
98-
if not discord_club:
97+
db_club = Club.get_or_none(Club.sith_id == club.id)
98+
role_membre = member.guild.get_role(db_club.member_role_id)
99+
if not db_club:
99100
await interaction.followup.send(f"Le club : {club.name} n'existe pas")
100101
return
101102
if (
102103
not interaction.user.guild_permissions.manage_roles
103-
and not interaction.user.get_role(discord_club.president_role_id)
104+
and not interaction.user.get_role(db_club.president_role_id)
104105
):
105106
await interaction.followup.send(
106107
"Seul le président du club et les admins peuvent ajouter un membre"
@@ -109,7 +110,7 @@ async def add_club_member(
109110
if role_membre in member.roles:
110111
await interaction.followup.send("Cet utilisateur est déjà dans le club")
111112
return
112-
await self.club_service.add_member(discord_club, member)
113+
await self.club_service.add_member(db_club, member)
113114
await interaction.followup.send(
114115
f"{member.name} a été ajouté au club :thumbs_up:"
115116
)
@@ -121,8 +122,10 @@ async def create_club(
121122
self, interaction: Interaction, club: Transform[ClubSchema, ClubTransformer]
122123
):
123124
await interaction.response.defer(thinking=True)
124-
discord_club = DiscordClub.load(club.id)
125-
if discord_club is not None:
125+
try:
126+
await self.club_service.create_club(club, interaction.guild)
127+
await interaction.followup.send(f"Le club : {club.name} à été créé")
128+
except ClubExists:
126129
await interaction.followup.send(f"Le club : {club.name} existe déjà...")
127130
else:
128131
await self.club_service.create_club(club, interaction.guild)
@@ -139,16 +142,16 @@ async def handover(
139142
new_treasurer: Member,
140143
):
141144
await interaction.response.defer(thinking=True)
142-
discord_club = DiscordClub.load(club.id)
145+
db_club = Club.get_or_none(Club.sith_id == club.id)
143146
guild = interaction.guild
144147

145-
if not discord_club:
148+
if not db_club:
146149
await interaction.followup.send(f"Le club : {club.name} n'existe pas")
147150
return
148151

149152
await self.club_service.handover(club, new_president, new_treasurer, guild)
150153
annonce = await self.club_service.get_channel(
151-
guild, discord_club.category_id, f"annonces {club.name}".lower()
154+
guild, db_club.category_id, f"annonces {club.name}".lower()
152155
)
153156

154157
if annonce:
@@ -157,12 +160,11 @@ async def handover(
157160
f"le nouveau président du club {club.name}"
158161
f" et {new_treasurer.mention} le nouveau trésorier !!"
159162
)
160-
161163
else:
162164
await interaction.followup.send(
163165
"Attention, ce club n'a pas ses salons de discussion.\n"
164166
"La passation va quand même se faire, mais il faut "
165-
"contacter un des mainteneurs du bots pour remettre "
167+
"contacter un des mainteneurs du bot pour remettre "
166168
"les salons en place"
167169
)
168170
await interaction.followup.send("Passation effectuée")
@@ -174,16 +176,15 @@ async def stop_club(
174176
self, interaction: Interaction, club: Transform[ClubSchema, ClubTransformer]
175177
):
176178
await interaction.response.defer(thinking=True)
177-
guild = interaction.guild
178-
discord_club = DiscordClub.load(club.id)
179-
await self.club_service.stop_club(discord_club, interaction.guild)
179+
db_club = Club.get_or_none(Club.sith_id == club.id)
180+
await self.club_service.stop_club(db_club, interaction.guild)
180181
annonce = await self.club_service.get_channel(
181-
guild, discord_club.category_id, f"annonces {club.name}".lower()
182+
interaction.guild, db_club.category_id, f"annonces {club.name}".lower()
182183
)
183184

184185
if annonce:
185186
await annonce.send(
186187
"Le club, n'ayant pas été repris, est "
187188
"temporairement fermé jusqu'à reprise du club"
188189
)
189-
await interaction.followup.send(f"Le club : {club.name} à été arrété")
190+
await interaction.followup.send(f"Le club : {club.name} à été arrêté")

src/services/club.py

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from __future__ import annotations
22

3-
import json
4-
from typing import TYPE_CHECKING, Self
3+
from typing import TYPE_CHECKING
54
from urllib.parse import urljoin
65

76
from discord import Embed, PermissionOverwrite, utils
8-
from pydantic import BaseModel
97

10-
from src.settings import BASE_DIR, Settings
8+
from db.models import Club
9+
from src.settings import Settings
1110

1211
if TYPE_CHECKING:
1312
from discord import Guild, Member
@@ -16,36 +15,16 @@
1615
from src.main import AeBot
1716

1817

19-
class DiscordClub(BaseModel):
20-
"""Pydantic model representing data about a club on the discord guild.
18+
class ClubError(Exception):
19+
"""Errors related to operations on clubs"""
2120

22-
It can also manage interaction with the internal data cache.
23-
"""
2421

25-
name: str
26-
sith_id: int
27-
category_id: int
28-
president_role_id: int
29-
president_sith_id: int | None = None
30-
treasurer_role_id: int
31-
treasurer_sith_id: int | None = None
32-
member_role_id: int
33-
member_sith_id: int | None = None
34-
former_member_role_id: int
22+
class ClubExists(ClubError):
23+
"""Trying to create a club that already exists"""
3524

36-
@classmethod
37-
def load_all(cls) -> dict[str, dict]:
38-
return json.loads((BASE_DIR / "data/club.json").read_text())
3925

40-
@classmethod
41-
def load(cls, club_id: int) -> Self | None:
42-
club = cls.load_all().get(str(club_id))
43-
return cls.model_validate(club) if club is not None else club
44-
45-
def save(self):
46-
all_clubs = self.load_all()
47-
all_clubs[str(self.sith_id)] = self.model_dump()
48-
(BASE_DIR / "data/club.json").write_text(json.dumps(all_clubs))
26+
class ClubDoesNotExist(ClubError):
27+
"""Trying to use a club that does not exist"""
4928

5029

5130
class ClubService:
@@ -62,7 +41,8 @@ async def search_club(
6241
) -> list[SimpleClubSchema]:
6342
clubs = await self._client.search_clubs(current)
6443
if clubs and only_existing:
65-
clubs = [c for c in clubs if str(c.id) in DiscordClub.load_all()]
44+
clubs_ids = [c[0] for c in Club.select(Club.sith_id).tuples()]
45+
clubs = [c for c in clubs if c.id in clubs_ids]
6646
return clubs if clubs is not None else []
6747

6848
async def get_club(self, club_id: int) -> ClubSchema | None:
@@ -98,6 +78,8 @@ def embed(self, club: ClubSchema) -> Embed:
9878
return embed
9979

10080
async def create_club(self, club: ClubSchema, guild: Guild):
81+
if Club.filter(Club.sith_id == club.id).exists():
82+
raise ClubExists
10183
# create the role for member, presidence and treasurer
10284
president = await guild.create_role(name=f"Responsable {club.name}")
10385
treasurer = await guild.create_role(name=f"Trésorier {club.name}")
@@ -129,38 +111,35 @@ async def create_club(self, club: ClubSchema, guild: Guild):
129111
)
130112
await category.create_text_channel(f"Général-{club.name}")
131113
await category.create_voice_channel(f"Général-{club.name}")
132-
new_club = DiscordClub(
133-
sith_id=club.id,
114+
Club.create(
134115
name=club.name,
116+
category_id=category.id,
117+
sith_id=club.id,
135118
president_role_id=president.id,
136119
treasurer_role_id=treasurer.id,
137120
member_role_id=member.id,
138121
former_member_role_id=former_member.id,
139-
category_id=category.id,
140122
)
141-
new_club.save()
142123

143-
async def add_member(self, club: DiscordClub, member: Member):
124+
async def add_member(self, club: Club, member: Member):
144125
role = utils.get(member.guild.roles, id=club.member_role_id)
145126
former = utils.get(member.guild.roles, id=club.former_member_role_id)
146127
if former in member.roles:
147128
await member.remove_roles(
148129
former, reason=f"{member.name} joined club {club.name}"
149130
)
150131
await member.add_roles(role, reason=f"{member.name} joined club {club.name}")
151-
club.save()
152132

153-
async def remove_member(self, club: DiscordClub, member: Member):
133+
async def remove_member(self, club: Club, member: Member):
154134
role = utils.get(member.guild.roles, id=club.member_role_id)
155135
former = utils.get(member.guild.roles, id=club.former_member_role_id)
156136
await member.remove_roles(role, reason=f"{member.name} left club {club.name}")
157137
await member.add_roles(former, reason=f"{member.name} left club {club.name}")
158-
club.save()
159138

160139
async def handover(
161140
self, club: ClubSchema, new_pres: Member, new_treso: Member, guild: Guild
162141
):
163-
club = DiscordClub.load(club.id)
142+
club = Club.get_or_none(Club.sith_id == club.id)
164143

165144
# removing former presidence and treasurer
166145
role_pres = utils.get(guild.roles, id=club.president_role_id)
@@ -190,7 +169,7 @@ async def handover(
190169
)
191170
await category.move(above=highest_inactive)
192171

193-
async def stop_club(self, club: DiscordClub, guild: Guild):
172+
async def stop_club(self, club: Club, guild: Guild):
194173
role_pres = utils.get(guild.roles, id=club.president_role_id)
195174
role_treso = utils.get(guild.roles, id=club.treasurer_role_id)
196175
role_member = utils.get(guild.roles, id=club.member_role_id)

uv.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)