aboutsummaryrefslogtreecommitdiffstats
path: root/backend/discord.py
diff options
context:
space:
mode:
authorGravatar Hassan Abouelela <[email protected]>2022-02-05 16:50:11 +0400
committerGravatar Hassan Abouelela <[email protected]>2022-02-05 17:20:40 +0400
commit134b2f70e4cf947744f1b061766bb37fe616ad65 (patch)
treeef6b95bc5a78528d91ae969f3cfd00bc8e5be8ed /backend/discord.py
parentAdd Helper Functions For Managing Roles (diff)
Overhaul Scope System
Adds discord role support to the pre-existing scopes system to power more complex access permissions. Signed-off-by: Hassan Abouelela <[email protected]>
Diffstat (limited to 'backend/discord.py')
-rw-r--r--backend/discord.py105
1 files changed, 96 insertions, 9 deletions
diff --git a/backend/discord.py b/backend/discord.py
index cf80cf3..51de26a 100644
--- a/backend/discord.py
+++ b/backend/discord.py
@@ -1,8 +1,13 @@
"""Various utilities for working with the Discord API."""
+
+import datetime
+import json
+import typing
+
import httpx
+from pymongo.database import Database
-from backend import constants
-from backend.models import discord_role, discord_user
+from backend import constants, models
async def fetch_bearer_token(code: str, redirect: str, *, refresh: bool) -> dict:
@@ -40,7 +45,7 @@ async def fetch_user_details(bearer_token: str) -> dict:
return r.json()
-async def get_role_info() -> list[discord_role.DiscordRole]:
+async def _get_role_info() -> list[models.DiscordRole]:
"""Get information about the roles in the configured guild."""
async with httpx.AsyncClient() as client:
r = await client.get(
@@ -49,11 +54,50 @@ async def get_role_info() -> list[discord_role.DiscordRole]:
)
r.raise_for_status()
- return [discord_role.DiscordRole(**role) for role in r.json()]
-
-
-async def get_member(member_id: str) -> discord_user.DiscordMember:
- """Get a member by ID from the configured guild."""
+ return [models.DiscordRole(**role) for role in r.json()]
+
+
+async def get_roles(
+ database: Database, *, force_refresh: bool = False
+) -> list[models.DiscordRole]:
+ """
+ Get a list of all roles from the cache, or discord API if not available.
+
+ If `force_refresh` is True, the cache is skipped and the roles are updated.
+ """
+ collection = database.get_collection("roles")
+
+ if force_refresh:
+ # Drop all values in the collection
+ await collection.delete_many({})
+
+ # `create_index` creates the index if it does not exist, or passes
+ # This handles TTL on role objects
+ await collection.create_index(
+ "inserted_at",
+ expireAfterSeconds=60 * 60 * 24, # 1 day
+ name="inserted_at",
+ )
+
+ roles = []
+ async for role in collection.find():
+ roles.append(models.DiscordRole(**json.loads(role["data"])))
+
+ if len(roles) == 0:
+ # Fetch roles from the API and insert into the database
+ roles = await _get_role_info()
+ await collection.insert_many({
+ "name": role.name,
+ "id": role.id,
+ "data": role.json(),
+ "inserted_at": datetime.datetime.now(tz=datetime.timezone.utc),
+ } for role in roles)
+
+ return roles
+
+
+async def _fetch_member_api(member_id: str) -> typing.Optional[models.DiscordMember]:
+ """Get a member by ID from the configured guild using the discord API."""
async with httpx.AsyncClient() as client:
r = await client.get(
f"{constants.DISCORD_API_BASE_URL}/guilds/{constants.DISCORD_GUILD}"
@@ -61,5 +105,48 @@ async def get_member(member_id: str) -> discord_user.DiscordMember:
headers={"Authorization": f"Bot {constants.DISCORD_BOT_TOKEN}"}
)
+ if r.status_code == 404:
+ return None
+
r.raise_for_status()
- return discord_user.DiscordMember(**r.json())
+ return models.DiscordMember(**r.json())
+
+
+async def get_member(
+ database: Database, user_id: str, *, force_refresh: bool = False
+) -> typing.Optional[models.DiscordMember]:
+ """
+ Get a member from the cache, or from the discord API.
+
+ If `force_refresh` is True, the cache is skipped and the entry is updated.
+ None may be returned if the member object does not exist.
+ """
+ collection = database.get_collection("discord_members")
+
+ if force_refresh:
+ await collection.delete_one({"user": user_id})
+
+ # `create_index` creates the index if it does not exist, or passes
+ # This handles TTL on member objects
+ await collection.create_index(
+ "inserted_at",
+ expireAfterSeconds=60 * 60, # 1 hour
+ name="inserted_at",
+ )
+
+ result = await collection.find_one({"user": user_id})
+
+ if result is not None:
+ return models.DiscordMember(**json.loads(result["data"]))
+
+ member = await _fetch_member_api(user_id)
+
+ if not member:
+ return None
+
+ await collection.insert_one({
+ "user": user_id,
+ "data": member.json(),
+ "inserted_at": datetime.datetime.now(tz=datetime.timezone.utc),
+ })
+ return member