diff --git a/bot/src/cogs/botinfo.py b/bot/src/cogs/botinfo.py index 2a4c3f5..fa160d6 100644 --- a/bot/src/cogs/botinfo.py +++ b/bot/src/cogs/botinfo.py @@ -31,7 +31,7 @@ class BotInfo(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ # Memory usage @@ -99,7 +99,7 @@ class BotInfo(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ embed = discord.Embed(color=interaction.client.user.color) @@ -115,7 +115,7 @@ class BotInfo(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ embed = discord.Embed(color=interaction.client.user.color) @@ -128,7 +128,7 @@ class BotInfo(commands.Cog): Returns ---------- - (int) The bot's ping in milliseconds + (int): The bot's ping in milliseconds """ return int(self.bot.latency * 1000) @@ -139,7 +139,7 @@ class BotInfo(commands.Cog): Returns ---------- - (str) The bot's uptime in human readable format + (str): The bot's uptime in human readable format """ now = time() @@ -157,7 +157,7 @@ async def setup(bot: commands.Bot): Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance """ await bot.add_cog(BotInfo(bot)) diff --git a/bot/src/cogs/dev_tools.py b/bot/src/cogs/dev_tools.py index cb9e3a3..b35954c 100644 --- a/bot/src/cogs/dev_tools.py +++ b/bot/src/cogs/dev_tools.py @@ -29,7 +29,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context activity_type (str): The type of activity to set the bot's status to message (str): The message to set the bot's status to status_type (str): The type of status to set the bot's status to @@ -60,7 +60,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context guild_id (int): The ID of the guild to leave """ @@ -104,7 +104,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context """ if not ctx.invoked_subcommand: @@ -117,7 +117,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context cog (str): The name of the cog to disable """ @@ -141,7 +141,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context cog (str): The name of the cog to enable """ @@ -165,7 +165,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context cog (str): The name of the cog to reload """ @@ -189,7 +189,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context """ extensions = [f"cogs.{file[:-3]}" for file in listdir("./src/cogs") if file.endswith(".py")] @@ -205,7 +205,7 @@ class DevTools(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context """ # Only loaded cogs can be reloaded. Copied to a list since reloading modifies bot.extensions @@ -267,7 +267,7 @@ async def setup(bot: commands.Bot): Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance """ await bot.add_cog(DevTools(bot)) diff --git a/bot/src/cogs/errors.py b/bot/src/cogs/errors.py index c30f3d5..6c9d5a3 100644 --- a/bot/src/cogs/errors.py +++ b/bot/src/cogs/errors.py @@ -27,7 +27,7 @@ class Errors(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object + ctx (commands.Context): The command context """ self.bot.logger.info( @@ -45,7 +45,7 @@ class Errors(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction command (app_commands.Command | app_commands.ContextMenu): Command object """ @@ -62,8 +62,8 @@ class Errors(commands.Cog): Parameters ---------- - ctx (commands.Context): Context object - error (commands.CommandError): Eror context object + ctx (commands.Context): The command context + error (commands.CommandError): The error the command raised """ # Failed commands don't count towards the cooldown, unless the failure is the cooldown itself. @@ -133,8 +133,8 @@ class Errors(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object - error (app_commands.AppCommandError): Eror context object + interaction (discord.Interaction): The slash command interaction + error (app_commands.AppCommandError): The error the command raised """ # The command may have deferred or responded before it failed @@ -196,7 +196,7 @@ async def setup(bot: commands.Bot): Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance """ await bot.add_cog(Errors(bot)) diff --git a/bot/src/cogs/galtinn.py b/bot/src/cogs/galtinn.py index 4797000..2e66159 100644 --- a/bot/src/cogs/galtinn.py +++ b/bot/src/cogs/galtinn.py @@ -42,6 +42,10 @@ class Galtinn(commands.Cog): self.session: aiohttp.ClientSession | None = None async def cog_load(self): + """ + Open the Galtinn session, set up the database listener and start the background tasks + """ + # One session for all Galtinn requests, so connections are reused self.session = aiohttp.ClientSession( headers={"Authorization": f"Token {self.bot.galtinn['auth_token']}"}, @@ -55,6 +59,10 @@ class Galtinn(commands.Cog): self.verification_cleanup.start() async def cog_unload(self): + """ + Stop the background tasks and close the database listener and the Galtinn session + """ + self.bot.logger.info("Unloading cog") self.membership_check.cancel() self.verification_cleanup.cancel() @@ -122,6 +130,10 @@ class Galtinn(commands.Cog): def on_listen_conn_terminated(self, conn: asyncpg.Connection): """ Starts reconnecting the listener if its connection is lost, e.g. on a database restart + + Parameters + ---------- + conn (asyncpg.Connection): The connection that was lost """ # Connections closed on cog unload are detached before closing @@ -153,6 +165,13 @@ class Galtinn(commands.Cog): async def process_notification(self, conn: asyncpg.Connection, pid: int, channel: str, payload: str): """ Applies Galtinn roles to a user that has just completed verification + + Parameters + ---------- + conn (asyncpg.Connection): The connection that received the notification + pid (int): The ID of the database process that sent the notification + channel (str): The notification channel + payload (str): The Discord user ID and the Galtinn user ID, separated by a space """ self.bot.logger.info("Received galtinn_auth_complete event from database") @@ -242,6 +261,10 @@ class Galtinn(commands.Cog): async def fetch_galtinn_users(self, discord_id: int | None = None) -> Users | None: """ Attempts to fetch user(s) from Galtinn. If no Discord ID is given, all users are fetched + + Parameters + ---------- + discord_id (int | None): The Discord ID of the user to fetch """ params = {"no_discord_id": False} @@ -255,6 +278,11 @@ class Galtinn(commands.Cog): ) -> DiscordProfiles | None: """ Attempts to fetch discord profile(s) from Galtinn. If no parameters are given, all profiles are fetched + + Parameters + ---------- + galtinn_user_id (int | None): Only fetch the profile of this Galtinn user + discord_id (int | None): Only fetch the profile with this Discord ID """ params = {} @@ -278,7 +306,7 @@ class Galtinn(commands.Cog): Returns ---------- - set(int): Set of role ids + (set[int]): Set of role ids """ all_roles = {self.bot.galtinn_roles["volunteer"], self.bot.galtinn_roles["member"]} @@ -493,7 +521,7 @@ class Galtinn(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ await interaction.response.defer(ephemeral=True) @@ -566,7 +594,7 @@ class Galtinn(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ # TODO: add confirmation button @@ -627,7 +655,7 @@ class Galtinn(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ # Discord ignores guild_only on subcommands, and the rest of the group has to work in DMs @@ -686,7 +714,7 @@ async def setup(bot: commands.Bot): Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance """ await bot.add_cog(Galtinn(bot)) diff --git a/bot/src/cogs/info.py b/bot/src/cogs/info.py index b4b24b9..dc496d9 100644 --- a/bot/src/cogs/info.py +++ b/bot/src/cogs/info.py @@ -31,7 +31,7 @@ class Info(commands.Cog): roles (list[discord.Role]): List of roles Returns - ------- + ---------- (str): String of roles """ @@ -50,11 +50,11 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction join_method (callable): Function to join the boosters with Returns - ------- + ---------- (str): String of boosters """ @@ -78,7 +78,7 @@ class Info(commands.Cog): members (list[discord.Member]): List of members Returns - ------- + ---------- (str): String of members """ @@ -98,7 +98,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ # Days since guild was created @@ -254,7 +254,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ roles = self.construct_role_string(interaction.guild.roles) @@ -277,7 +277,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ if len(interaction.guild.premium_subscribers) == 0: @@ -302,7 +302,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ if not interaction.guild.icon: @@ -323,7 +323,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ if not interaction.guild.splash: @@ -344,7 +344,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ if not interaction.guild.banner: @@ -367,7 +367,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction role (discord.Role): Role to fetch information about """ @@ -413,7 +413,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction channel (discord.TextChannel): Text channel to fetch information about """ @@ -447,7 +447,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction channel (discord.VoiceChannel): Voice channel to fetch information about """ @@ -472,7 +472,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ roles = sorted( @@ -501,7 +501,7 @@ class Info(commands.Cog): Parameters ---------- - ctx (commands.Context): Command context object + ctx (commands.Context): The command context emoji (discord.Emoji): Emoji to fetch information about """ @@ -534,7 +534,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ # Sort members by creation date @@ -566,7 +566,7 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ # Sort members by creation date @@ -597,8 +597,8 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object - user (discord.Member, optional): User to get information about. Defaults to None (invoker). + interaction (discord.Interaction): The slash command interaction + user (discord.Member | None): User to get information about. Defaults to the invoking user """ if not user: @@ -693,8 +693,8 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object - user (discord.Member, optional): User to get roles of. Defaults to None (invoker). + interaction (discord.Interaction): The slash command interaction + user (discord.Member | None): User to get roles of. Defaults to the invoking user """ if not user: @@ -724,8 +724,8 @@ class Info(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object - user (discord.User, optional): User to get avatar of. Defaults to None (invoker). + interaction (discord.Interaction): The slash command interaction + user (discord.User | None): User to get avatar of. Defaults to the invoking user """ if not user: @@ -743,7 +743,7 @@ async def setup(bot: commands.Bot): Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance """ await bot.add_cog(Info(bot)) diff --git a/bot/src/cogs/utils/discord_utils.py b/bot/src/cogs/utils/discord_utils.py index 65f7bcd..38989f8 100644 --- a/bot/src/cogs/utils/discord_utils.py +++ b/bot/src/cogs/utils/discord_utils.py @@ -12,7 +12,7 @@ async def send_as_txt_file(interaction: discord.Interaction, content: str, filen Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction content (str): String that's too long to send filename (str): Name of the file shown in Discord """ @@ -27,7 +27,7 @@ async def get_discord_guild(bot: commands.Bot, id: int) -> discord.Guild | None: Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance id (int): ID of the object Returns @@ -56,7 +56,7 @@ async def get_guild_member(bot: commands.Bot, guild: discord.Guild, user_id: int Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance guild (discord.Guild): The discord guild to fetch the member from user_id (int): ID of the user @@ -87,7 +87,7 @@ async def get_guild_roles(bot: commands.Bot, guild: discord.Guild, role_ids: set Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance guild (discord.Guild): The discord guild to fetch the roles from role_ids (set[int]): IDs of the roles @@ -123,11 +123,13 @@ class ScrollerButton(discord.ui.Button): ): """ Parameters - ----------- + ---------- paginator (Paginator): The paginator object that contains the data to be paginated button_action (callable): The function that returns the requested page content_constructor (callable): A function that takes a paginator object and a page number and returns an embed - owner (discord.User|discord.Member): The user that invoked the paginator. Only this user can use the button + owner (discord.User | discord.Member): The user that invoked the paginator. Only this user can use the button + label (str): The text on the button + disabled (bool): Whether the button is greyed out """ super().__init__(label=label, disabled=disabled) @@ -141,8 +143,8 @@ class ScrollerButton(discord.ui.Button): What to do when the button is pressed Parameters - ----------- - interaction (discord.Interaction): Slash command context object + ---------- + interaction (discord.Interaction): The interaction that triggered this """ if interaction.user.id != self.owner.id: @@ -163,20 +165,20 @@ class Scroller(discord.ui.View): def __init__( self, - paginatior: misc_utils.Paginator, + paginator: misc_utils.Paginator, owner: discord.User | discord.Member, content_constructor: callable = None, ): """ Parameters - ----------- + ---------- paginator (Paginator): The paginator object that contains the data to be paginated - owner (discord.User|discord.Member): The user that invoked the paginator. Only this user can use the buttons + owner (discord.User | discord.Member): The user that invoked the paginator. Only this user can use the buttons content_constructor (callable): A function that takes a paginator object and a page number and returns an embed """ super().__init__() - self.paginator = paginatior + self.paginator = paginator self.content_constructor = content_constructor if content_constructor else self.__default_content_constructor self.add_item( @@ -225,7 +227,7 @@ class Scroller(discord.ui.View): Constructs the embed to be displayed Parameters - ----------- + ---------- base_embed (discord.Embed): The base embed to add fields to """ @@ -237,8 +239,7 @@ class Scroller(discord.ui.View): Parameters ---------- - paginator (Paginator): Paginator dataclass - page (list): List of streaks to display on a page + page (list): The items to show on the page embed (discord.Embed): Embed to add fields to """ diff --git a/bot/src/cogs/utils/embed_templates.py b/bot/src/cogs/utils/embed_templates.py index adf4db7..e98f174 100644 --- a/bot/src/cogs/utils/embed_templates.py +++ b/bot/src/cogs/utils/embed_templates.py @@ -6,11 +6,11 @@ def error_warning(text: str) -> discord.Embed: Creates an embed with a specified error message based on a warning template Parameters - ----------- + ---------- text (str): The error message Returns - ----------- + ---------- (discord.Embed): An embed object based on the template with the specified text """ @@ -23,11 +23,11 @@ def error_fatal(text: str) -> discord.Embed: Creates an embed with a specified error message based on a warning template Parameters - ----------- + ---------- text (str): The error message Returns - ----------- + ---------- (discord.Embed): An embed object based on the template with the specified text """ @@ -40,12 +40,12 @@ def success(text: str) -> discord.Embed: Creates an embed with a specified message using a template signifying success. Parameters - ----------- + ---------- text (str): The message Returns - ----------- - discord.Embed: An embed object based on the template with the specified text + ---------- + (discord.Embed): An embed object based on the template with the specified text """ embed = discord.Embed(color=discord.Color.green(), description=f"✅ {text}") diff --git a/bot/src/cogs/utils/misc_utils.py b/bot/src/cogs/utils/misc_utils.py index 6a246c9..f239b4f 100644 --- a/bot/src/cogs/utils/misc_utils.py +++ b/bot/src/cogs/utils/misc_utils.py @@ -66,7 +66,7 @@ class Paginator: Returns ---------- - (list|None): The page you requested + (list | None): The page you requested """ if page < 1 or page > self.total_page_count: diff --git a/bot/src/cogs/viteboka.py b/bot/src/cogs/viteboka.py index 07f4c98..fa5636a 100644 --- a/bot/src/cogs/viteboka.py +++ b/bot/src/cogs/viteboka.py @@ -26,7 +26,7 @@ async def query_api(session: aiohttp.ClientSession, params: dict) -> dict: Returns ---------- - dict: The JSON response + (dict): The JSON response Raises ---------- @@ -53,7 +53,7 @@ async def fetch_article(session: aiohttp.ClientSession, title: str): Returns ---------- - tuple: The title, url, text and image of the article + (tuple): The title, url, text and image of the article """ # The wiki renders the article to HTML for us, so templates are already expanded @@ -194,7 +194,7 @@ def viteboka_embed(title: str, url: str, text: str, image: str): Returns ---------- - discord.Embed: The embed + (discord.Embed): The embed """ embed = discord.Embed(color=discord.Color.orange(), title=title, url=url, description=text) @@ -205,7 +205,7 @@ def viteboka_embed(title: str, url: str, text: str, image: str): class VitebokaException(Exception): - pass + """Raised when an article can't be fetched. The message is shown to the user""" class Viteboka(commands.Cog): @@ -233,7 +233,7 @@ class Viteboka(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction query (str): The search query """ @@ -292,7 +292,7 @@ class Viteboka(commands.Cog): Parameters ---------- - interaction (discord.Interaction): Slash command context object + interaction (discord.Interaction): The slash command interaction """ await interaction.response.defer() @@ -336,8 +336,8 @@ class ArticleButton(discord.ui.Button): ): """ Parameters - ----------- - owner (discord.User|discord.Member): The user that invoked the paginator. Only this user can use the button + ---------- + owner (discord.User | discord.Member): The user that invoked the paginator. Only this user can use the button label (str): The label of the button article_title (str): The title of the article the button is associated with """ @@ -351,8 +351,8 @@ class ArticleButton(discord.ui.Button): What to do when the button is pressed Parameters - ----------- - interaction (discord.Interaction): Slash command context object + ---------- + interaction (discord.Interaction): The interaction that triggered this """ await interaction.response.defer() @@ -378,7 +378,7 @@ async def setup(bot: commands.Bot): Parameters ---------- - bot (commands.Bot): Bot instance + bot (commands.Bot): The bot instance """ await bot.add_cog(Viteboka(bot)) diff --git a/bot/src/logger.py b/bot/src/logger.py index 6a79d1a..2e24724 100644 --- a/bot/src/logger.py +++ b/bot/src/logger.py @@ -8,6 +8,10 @@ class BotLogger: """Logging config for the bot""" def __init__(self): + """ + Log to the console and to a file that rotates at midnight + """ + logger = logging.getLogger("") logger.setLevel(logging.INFO) formatter = logging.Formatter( diff --git a/bot/src/models.py b/bot/src/models.py index 8bde5f5..d8930f2 100644 --- a/bot/src/models.py +++ b/bot/src/models.py @@ -5,12 +5,16 @@ from pydantic import Field class GroupDiscordRole(BaseModel): + """A Discord role connected to a Galtinn group""" + id: int discord_id: int description: str class GroupProfile(BaseModel): + """Extra information about a Galtinn group, including its Discord roles""" + id: int posix_name: str description: str @@ -19,18 +23,24 @@ class GroupProfile(BaseModel): class Group(BaseModel): + """A Galtinn group""" + id: int name: str profile: Optional[GroupProfile] class UserDiscordProfile(BaseModel): + """The link between a Galtinn user and a Discord user""" + id: int discord_id: int user: int class DuskenUser(BaseModel): + """A Galtinn user""" + id: int username: str is_volunteer: bool @@ -40,6 +50,8 @@ class DuskenUser(BaseModel): class BasicResponse(BaseModel): + """A page of results from a paginated Galtinn endpoint""" + count: int next: Optional[str] previous: Optional[str] @@ -47,12 +59,18 @@ class BasicResponse(BaseModel): class Users(BasicResponse): + """A page of Galtinn users""" + results: list[DuskenUser] class DiscordProfiles(BasicResponse): + """A page of Discord profiles""" + results: list[UserDiscordProfile] class Groups(BasicResponse): + """A page of Galtinn groups""" + results: list[Group] diff --git a/bot/src/run.py b/bot/src/run.py index fff78ea..8d8687a 100644 --- a/bot/src/run.py +++ b/bot/src/run.py @@ -16,7 +16,13 @@ load_dotenv() class Bot(commands.Bot): + """The bot, configured from environment variables""" + def __init__(self): + """ + Read the configuration from the environment + """ + super().__init__( command_prefix=commands.when_mentioned_or(os.environ["BOT_PREFIX"]), case_insensitive=True, @@ -48,6 +54,10 @@ class Bot(commands.Bot): } async def setup_hook(self): + """ + Open the HTTP session and database pool, load the cogs and sync the slash commands + """ + self.http_session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=15)) # DB needs to be setup here because contructor is not async @@ -97,6 +107,10 @@ class Bot(commands.Bot): """ Close the HTTP session and database pool after the bot has shut down. This isn't done in close(), because bot.run doesn't wait for a close() started by the SIGTERM handler to finish + + Parameters + ---------- + *args (tuple): The exception type, value and traceback if the bot stopped because of an error """ await super().__aexit__(*args) @@ -112,6 +126,10 @@ bot = Bot() @bot.event async def on_ready(): + """ + Print the bot's info and set its presence once it's connected + """ + if not hasattr(bot, "uptime"): bot.uptime = time() diff --git a/verification_server/src/server.py b/verification_server/src/server.py index dde7eb1..1f4c3d7 100644 --- a/verification_server/src/server.py +++ b/verification_server/src/server.py @@ -21,6 +21,14 @@ GALTINN_AUTH_TOKEN = os.environ["GALTINN_AUTH_TOKEN"] @asynccontextmanager async def lifespan(app: FastAPI): + """ + Open the database pool and HTTP session on startup, and close them on shutdown + + Parameters + ---------- + app (FastAPI): The app + """ + credentials = { "host": os.environ["DATABASE_HOST"], "database": os.environ["DATABASE_NAME"], @@ -45,11 +53,25 @@ templates = Jinja2Templates(directory="src/templates") @app.get("/") async def index(): + """ + Tell visitors there's nothing to see here + """ + return "Hei du! Her skal ikke du drive og luske!" @app.get("/callback") async def callback(request: Request, state: str, code: str | None = None): + """ + Finish a Galtinn login: link the Galtinn user to the Discord user and tell the bot + + Parameters + ---------- + request (Request): The request + state (str): Identifies the pending verification + code (str | None): The authorization code from Galtinn. Missing if the login was cancelled or failed + """ + # Claim the pending verification. Deleting it up front makes the state single use verification = await app.state.pool.fetchrow( """ @@ -175,4 +197,13 @@ async def callback(request: Request, state: str, code: str | None = None): @app.get("/success/{name}") async def success(request: Request, name: str): + """ + Show the success page after a verification + + Parameters + ---------- + request (Request): The request + name (str): The Galtinn username to show + """ + return templates.TemplateResponse(request, "success.html", {"name": name})