Skip to content

Commit 85db54b

Browse files
authored
Move the config database to be postgres. Remove mongodb from the bot (#736)
This moves the way guild config is accessed to be a dict instead of always pulling from the database. This moves config to be held in postgres instead of mongodb This removes mongodb from the bot completely
1 parent 6da8e1f commit 85db54b

34 files changed

+322
-486
lines changed

Documentation/Extension-howto.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ self.bot.file_config.group.subgroup.key
267267
---
268268
To access the json config, you can add the following line of code, which loads the guild config file:
269269
```py
270-
config = await self.bot.get_context_config(guild=ctx.guild)
270+
config = self.bot.guild_configs[guild_id]
271271
```
272272

273273
Afterwards you can access the values with

Documentation/TestCases.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Application
22
Uses Google forum for Application
3-
Uses Mongo for storing the application
3+
Uses Postgres for storing the application
44
Config setup
55
## Command Restrictions
66
.application get <application_id>
@@ -713,7 +713,7 @@ Discord Library Random
713713
Will send a deny embed "I ran into an error processing your command: Converting to "int" failed for parameter "max"." if cannot find an int for second parameter
714714

715715
# Rules
716-
Uses Mongo
716+
Uses Postgres
717717
## Command Restrictions
718718
.rule
719719
None

Pipfile

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ ib3 = "==0.2.0"
1818
inflect = "==7.0.0"
1919
irc = "==20.1.0"
2020
isort = "==5.12.0"
21-
motor = "==3.3.1"
2221
munch = "==4.0.0"
2322
typing_extensions = "==4.8.0"
2423
pip = "==23.3.1"

Pipfile.lock

+142-245
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+2-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ cp config.default.yml config.yml
2525
```
2626
### .env file
2727
The first file we will edit is the .env file. This is where you will store database information.
28-
You will need to create a username and password for mongodb and postgres. These credentials do not have to be different.
28+
You will need to create a username and password postgres.
2929
You will also need to create a db name for postgres. This works best when it is all lowercase, but it is not strictly required.
3030
When filling in the information, do not include spaces or quotes. Just put the content directly after the equals sign.
3131
You will need all of this information again, so make sure to keep note of it.
@@ -36,9 +36,6 @@ For the admin ID, get your user ID by right clicking on your name, either on the
3636
#### postgres
3737
For postgres, you will need the username, password, and DB name you created previously. Enter it exactly as found in your .env file.
3838
Do not change the port or host.
39-
#### mongodb
40-
For mongodb, you will need the username and password you created previously. You will also need to create a DB name here. Enter the username and password exactly as found in your .env file. Just like postgres, the DB name works best with all lowercase, but it is not a requirement.
41-
Do not change the port or host.
4239
#### Additional configuration
4340
All the additional configuration is optional, and is not required to start the bot. This includes all API keys. The default settings everywhere else work, but can be changed later if desired.
4441
## Final tasks
@@ -89,4 +86,4 @@ class Greeter(cogs.BaseCog):
8986
async def hello(self, ctx):
9087
await self.hello_command(ctx)
9188
```
92-
Extensions can be configured per-guild with settings saved on MongoDB. There are several extensions included in the main repo, so please reference them for more advanced examples.
89+
Extensions can be configured per-guild with settings saved on Postgres. There are several extensions included in the main repo, so please reference them for more advanced examples.

config.default.yml

-6
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@ database:
1313
name:
1414
host: postgres
1515
port: 5432
16-
mongodb:
17-
user:
18-
password:
19-
name:
20-
host: mongodb
21-
port: 27017
2216
api:
2317
github:
2418
api_key:

default.env

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
MONGO_DB_PASSWORD=
2-
MONGO_DB_USER=
31
POSTGRES_DB_NAME=
42
POSTGRES_DB_PASSWORD=
53
POSTGRES_DB_USER=

docker-compose.yml

-15
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ services:
1616
- all
1717
depends_on:
1818
- postgres
19-
- mongodb
2019

2120
postgres:
2221
image: postgres:15.3-alpine
@@ -33,20 +32,6 @@ services:
3332
networks:
3433
- all
3534

36-
mongodb:
37-
image: mongo:6.0.7
38-
container_name: mongodb
39-
volumes:
40-
- ../DBs/mongo:/data/db
41-
ports:
42-
- "127.0.0.1:27017:27017"
43-
environment:
44-
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_DB_PASSWORD}
45-
- MONGO_INITDB_ROOT_USERNAME=${MONGO_DB_USER}
46-
restart: unless-stopped
47-
networks:
48-
- all
49-
5035
networks:
5136
all:
5237
driver: bridge

techsupport_bot/base/advanced.py

+58-89
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
import asyncio
44
import datetime
5+
import json
56
import random
67
import re
78
import string
8-
import time
99

1010
import discord
1111
import error as custom_errors
@@ -24,15 +24,14 @@ class AdvancedBot(data.DataBot):
2424
including per-guild config and event logging.
2525
"""
2626

27-
GUILD_CONFIG_COLLECTION = "guild_config"
2827
CONFIG_RECEIVE_WARNING_TIME_MS = 1000
2928
DM_GUILD_ID = "dmcontext"
3029

3130
def __init__(self, *args, **kwargs):
3231
self.owner = None
3332
self.__startup_time = None
34-
self.guild_config_collection = None
3533
self.guild_config_lock = None
34+
self.guild_configs = {}
3635
super().__init__(*args, prefix=self.get_prefix, **kwargs)
3736
self.guild_config_cache = expiringdict.ExpiringDict(
3837
max_len=self.file_config.cache.guild_config_cache_length,
@@ -52,92 +51,35 @@ async def start(self, *args, **kwargs):
5251
self.guild_config_lock = asyncio.Lock()
5352
await super().start(*args, **kwargs)
5453

55-
async def get_prefix(self, message):
54+
async def get_prefix(self, message: discord.Message) -> str:
5655
"""Gets the appropriate prefix for a command.
5756
5857
parameters:
5958
message (discord.Message): the message to check against
6059
"""
61-
guild_config = await self.get_context_config(guild=message.guild)
60+
guild_config = self.guild_configs[str(message.guild.id)]
6261
return getattr(
6362
guild_config, "command_prefix", self.file_config.bot_config.default_prefix
6463
)
6564

66-
async def get_all_context_configs(self, projection, limit=100):
67-
"""Gets all context configs.
65+
async def register_new_guild_config(self, guild: str) -> bool:
66+
"""This creates a config for a new guild if needed
6867
69-
parameters:
70-
projection (dict): the MongoDB projection for returned data
71-
limit (int): the max number of config objects to return
72-
"""
73-
configs = []
74-
cursor = self.guild_config_collection.find({}, projection)
75-
for document in await cursor.to_list(length=limit):
76-
configs.append(munch.DefaultMunch.fromDict(document, None))
77-
return configs
78-
79-
async def get_context_config(
80-
self, ctx=None, guild=None, create_if_none=True, get_from_cache=True
81-
):
82-
"""Gets the appropriate config for the context.
68+
Args:
69+
guild (str): The id of the guild to create config for, in string form
8370
84-
parameters:
85-
ctx (discord.ext.Context): the context of the config
86-
guild (discord.Guild): the guild associated with the config (provided instead of ctx)
87-
create_if_none (bool): True if the config should be created if not found
88-
get_from_cache (bool): True if the config should be fetched from the cache
71+
Returns:
72+
bool: True if a config was created, False if a config already existed
8973
"""
90-
start = time.time()
91-
92-
if ctx:
93-
guild_from_ctx = getattr(ctx, "guild", None)
94-
lookup = guild_from_ctx.id if guild_from_ctx else self.DM_GUILD_ID
95-
elif guild:
96-
lookup = guild.id
97-
else:
98-
return None
99-
100-
lookup = str(lookup)
101-
102-
config_ = None
103-
104-
if get_from_cache:
105-
config_ = self.guild_config_cache.get(lookup)
106-
107-
if not config_:
108-
# locking prevents duplicate configs being made
109-
async with self.guild_config_lock:
110-
config_ = await self.guild_config_collection.find_one(
111-
{"guild_id": {"$eq": lookup}}
112-
)
113-
114-
if not config_:
115-
await self.logger.send_log(
116-
message="No config found in MongoDB",
117-
level=LogLevel.DEBUG,
118-
console_only=True,
119-
)
120-
if create_if_none:
121-
config_ = await self.create_new_context_config(lookup)
122-
else:
123-
config_ = await self.sync_config(config_)
124-
125-
if config_:
126-
self.guild_config_cache[lookup] = config_
127-
128-
time_taken = (time.time() - start) * 1000.0
129-
130-
if time_taken > self.CONFIG_RECEIVE_WARNING_TIME_MS:
131-
await self.logger.send_log(
132-
message=(
133-
f"Context config receive time = {time_taken} ms (over"
134-
f" {self.CONFIG_RECEIVE_WARNING_TIME_MS} threshold)"
135-
),
136-
level=LogLevel.WARNING,
137-
context=LogContext(guild=self.get_guild(lookup)),
138-
)
139-
140-
return config_
74+
async with self.guild_config_lock:
75+
try:
76+
config = self.guild_configs[guild]
77+
except KeyError:
78+
config = None
79+
if not config:
80+
await self.create_new_context_config(guild)
81+
return True
82+
return False
14183

14284
async def create_new_context_config(self, lookup: str):
14385
"""Creates a new guild config based on a lookup key (usually a guild ID).
@@ -178,18 +120,43 @@ async def create_new_context_config(self, lookup: str):
178120
context=LogContext(guild=self.get_guild(lookup)),
179121
console_only=True,
180122
)
181-
await self.guild_config_collection.insert_one(config_)
123+
# Modify the database
124+
await self.write_new_config(str(lookup), json.dumps(config_))
125+
126+
# Modify the local cache
127+
self.guild_configs[lookup] = config_
128+
182129
except Exception as exception:
183130
# safely finish because the new config is still useful
184131
await self.logger.send_log(
185-
message="Could not insert guild config into MongoDB",
132+
message="Could not insert guild config into Postgres",
186133
level=LogLevel.ERROR,
187134
context=LogContext(guild=self.get_guild(lookup)),
188135
exception=exception,
189136
)
190137

191138
return config_
192139

140+
async def write_new_config(self, guild_id: str, config: str) -> None:
141+
"""Takes a config and guild and updates the config in the database
142+
This is rarely needed
143+
144+
Args:
145+
guild_id (str): The str ID of the guild the config belongs to
146+
config (str): The str representation of the json config
147+
"""
148+
database_config = await self.models.Config.query.where(
149+
self.models.Config.guild_id == guild_id
150+
).gino.first()
151+
if database_config:
152+
await database_config.update(config=str(config)).apply()
153+
else:
154+
new_database_config = self.models.Config(
155+
guild_id=str(guild_id),
156+
config=str(config),
157+
)
158+
await new_database_config.create()
159+
193160
async def sync_config(self, config_object):
194161
"""Syncs the given config with the currently loaded extensions.
195162
@@ -229,10 +196,14 @@ async def sync_config(self, config_object):
229196
context=LogContext(guild=self.get_guild(config_object.guild_id)),
230197
console_only=True,
231198
)
232-
await self.guild_config_collection.replace_one(
233-
{"_id": config_object.get("_id")}, config_object
199+
# Modify the database
200+
await self.write_new_config(
201+
str(config_object.guild_id), json.dumps(config_object)
234202
)
235203

204+
# Modify the local cache
205+
self.guild_configs[config_object.guild_id] = config_object
206+
236207
return config_object
237208

238209
async def can_run(self, ctx: commands.Context, *, call_once=False):
@@ -249,7 +220,7 @@ async def can_run(self, ctx: commands.Context, *, call_once=False):
249220
console_only=True,
250221
)
251222
is_bot_admin = await self.is_bot_admin(ctx)
252-
config = await self.get_context_config(ctx)
223+
config = self.guild_configs[str(ctx.guild.id)]
253224

254225
# Rate limiter
255226
if config.rate_limit.get("enabled", False):
@@ -264,8 +235,6 @@ async def can_run(self, ctx: commands.Context, *, call_once=False):
264235
if ctx.message.id not in self.command_execute_history[identifier]:
265236
self.command_execute_history[identifier][ctx.message.id] = True
266237

267-
print(self.command_execute_history[identifier])
268-
269238
if (
270239
len(self.command_execute_history[identifier])
271240
> config.rate_limit.commands
@@ -280,7 +249,7 @@ async def can_run(self, ctx: commands.Context, *, call_once=False):
280249

281250
extension_name = self.get_command_extension_name(ctx.command)
282251
if extension_name:
283-
config = await self.get_context_config(ctx)
252+
config = self.guild_configs[str(ctx.guild.id)]
284253
if not extension_name in config.enabled_extensions:
285254
raise custom_errors.ExtensionDisabled
286255

@@ -354,7 +323,7 @@ def startup_time(self):
354323
"""Gets the startup timestamp of the bot."""
355324
return self.__startup_time
356325

357-
async def get_log_channel_from_guild(self, guild, key):
326+
async def get_log_channel_from_guild(self, guild: discord.Guild, key: str):
358327
"""Gets the log channel ID associated with the given guild.
359328
360329
This also checks if the channel exists in the correct guild.
@@ -366,8 +335,8 @@ async def get_log_channel_from_guild(self, guild, key):
366335
if not guild:
367336
return None
368337

369-
config_ = await self.get_context_config(guild=guild)
370-
channel_id = config_.get(key)
338+
config = self.guild_configs[str(guild.id)]
339+
channel_id = config.get(key)
371340

372341
if not channel_id:
373342
return None
@@ -459,7 +428,7 @@ def format_username(self, username: str) -> str:
459428

460429
async def on_member_join(self, member: discord.Member):
461430
"""See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join"""
462-
config = await self.get_context_config(guild=member.guild)
431+
config = self.guild_configs[str(member.guild.id)]
463432

464433
if config.get("nickname_filter", False):
465434
temp_name = self.format_username(member.display_name)

0 commit comments

Comments
 (0)