-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement partial puzzle cog (#6)
- Loading branch information
Showing
16 changed files
with
320 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -157,4 +157,7 @@ cython_debug/ | |
bin/** | ||
lib64/** | ||
pyvenv.cfg | ||
lib64 | ||
lib64 | ||
|
||
# docker volumne | ||
pg_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from datetime import datetime | ||
from zoneinfo import ZoneInfo | ||
import discord | ||
from discord.ext import commands | ||
from discord import app_commands | ||
|
||
from src.queries.puzzle import find_puzzle, create_puzzle | ||
from src.queries.submission import create_submission | ||
from src.utils.decorators import in_team_channel | ||
from src.context.puzzle import can_access_puzzle, get_accessible_puzzles | ||
|
||
|
||
class Puzzle(commands.GroupCog): | ||
def __init__(self, bot): | ||
self.bot = bot | ||
|
||
@app_commands.command(name="submit", description="Submit an answer to a puzzle") | ||
@in_team_channel | ||
async def submit_answer( | ||
self, interaction: discord.Interaction, puzzle_id: str, answer: str | ||
): | ||
puzzle = await find_puzzle(puzzle_id) | ||
if not puzzle or not can_access_puzzle(puzzle, interaction.user.id): | ||
return await interaction.response.send_message( | ||
"No puzzle with the corresponding id exist!" | ||
) | ||
# TODO: retrieve team name | ||
team_name = "any" | ||
submission_is_correct = puzzle.puzzle_answer != answer | ||
await create_submission( | ||
puzzle_id, | ||
team_name, | ||
datetime.now(tz=ZoneInfo("Australia/Sydney")), | ||
answer, | ||
submission_is_correct, | ||
) | ||
|
||
if not submission_is_correct: | ||
return await interaction.response.send_message( | ||
"The submitted answer is incorrect!" | ||
) | ||
|
||
await interaction.response.send_message("The submitted answer is ...CORRECT!") | ||
|
||
@app_commands.command(name="list", description="List the available puzzles") | ||
@in_team_channel | ||
async def list_puzzles(self, interaction: discord.Interaction): | ||
puzzles = await get_accessible_puzzles(interaction.user.id) | ||
embed = discord.Embed(title="Current Puzzles", color=discord.Color.greyple()) | ||
|
||
puzzle_ids, puzzle_links = zip( | ||
*[ | ||
(puzzle.puzzle_id, f"[{puzzle.puzzle_name}]({puzzle.puzzle_link})") | ||
for puzzle in puzzles | ||
] | ||
) | ||
|
||
embed.add_field(name="ID", value="\n".join(puzzle_ids), inline=True) | ||
embed.add_field(name="Puzzles", value="\n".join(puzzle_links), inline=True) | ||
await interaction.response.send_message(embed=embed) | ||
|
||
@app_commands.command( | ||
name="create", description="Create a puzzle (must have admin role)" | ||
) | ||
async def list_puzzles( | ||
self, | ||
interaction: discord.Interaction, | ||
puzzle_name: str, | ||
): | ||
# surely there's a better way to do this | ||
if "Executives" not in [role.name for role in interaction.user.roles]: | ||
return await interaction.response.send_message( | ||
f"You don't have permission to do this!" | ||
) | ||
|
||
await interaction.response.send_message(f"Puzzle {puzzle_name} created!") | ||
|
||
|
||
async def setup(bot: commands.Bot): | ||
await bot.add_cog(Puzzle(bot)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[pytest] | ||
testpaths = tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from typing import List | ||
from src.models.puzzle import Puzzle | ||
from src.queries.puzzle import find_puzzles | ||
|
||
NUMBER_OF_FEEDERS = {"UTS": 4, "UNSW": 4, "USYD": 6} | ||
|
||
|
||
async def can_access_puzzle(puzzle: Puzzle, discord_id: int) -> bool: | ||
# TODO: implementation | ||
return True | ||
|
||
|
||
async def get_accessible_puzzles(discord_id: int) -> List[Puzzle]: | ||
# TODO: implementation | ||
puzzles = await find_puzzles() | ||
return puzzles |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from dataclasses import dataclass | ||
|
||
|
||
@dataclass | ||
class Puzzle: | ||
puzzle_id: str | ||
puzzle_name: str | ||
puzzle_answer: str | ||
puzzle_author: str | ||
puzzle_link: str | ||
uni: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from dataclasses import dataclass | ||
from datetime import datetime | ||
|
||
|
||
@dataclass | ||
class Submission: | ||
puzzle_id: str | ||
team_name: str | ||
submission_time: datetime | ||
submission_answer: str | ||
submission_is_correct: bool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import psycopg | ||
from psycopg.rows import class_row | ||
|
||
from src.config import config | ||
from src.models.puzzle import Puzzle | ||
|
||
DATABASE_URL = config["DATABASE_URL"] | ||
|
||
|
||
async def find_puzzle(puzzle_id: str): | ||
async with await psycopg.AsyncConnection.connect(DATABASE_URL) as aconn: | ||
async with aconn.cursor(row_factory=class_row(Puzzle)) as acur: | ||
await acur.execute( | ||
"SELECT * FROM public.puzzles WHERE puzzle_id = %s", (puzzle_id,) | ||
) | ||
return await acur.fetchone() | ||
|
||
|
||
async def find_puzzles(): | ||
async with await psycopg.AsyncConnection.connect(DATABASE_URL) as aconn: | ||
async with aconn.cursor(row_factory=class_row(Puzzle)) as acur: | ||
await acur.execute("SELECT * FROM public.puzzles") | ||
return await acur.fetchall() | ||
|
||
|
||
async def create_puzzle( | ||
puzzle_id: str, | ||
puzzle_name: str, | ||
puzzle_answer: str, | ||
puzzle_author: str, | ||
puzzle_link: str, | ||
uni: str, | ||
): | ||
async with await psycopg.AsyncConnection.connect(DATABASE_URL) as aconn: | ||
async with aconn.cursor() as acur: | ||
await acur.execute( | ||
""" | ||
INSERT INTO public.puzzles | ||
(puzzle_id, puzzle_name, puzzle_answer, puzzle_author, puzzle_link, uni) | ||
VALUES (%s, %s, %s, %s, %s, %s) | ||
""", | ||
( | ||
puzzle_id, | ||
puzzle_name, | ||
puzzle_answer, | ||
puzzle_author, | ||
puzzle_link, | ||
uni, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import psycopg | ||
from psycopg.rows import class_row | ||
from datetime import datetime | ||
|
||
from src.config import config | ||
from src.models.submission import Submission | ||
|
||
DATABASE_URL = config["DATABASE_URL"] | ||
|
||
|
||
async def create_submission( | ||
puzzle_id: str, | ||
team_name: str, | ||
submission_time: datetime, | ||
submission_answer: str, | ||
submission_is_correct: bool, | ||
): | ||
async with await psycopg.AsyncConnection.connect(DATABASE_URL) as aconn: | ||
async with aconn.cursor() as acur: | ||
await acur.execute( | ||
""" | ||
INSERT INTO public.submissions | ||
(puzzle_id, team_name, submission_time, submission_answer, submission_is_correct) | ||
VALUES (%s, %s, %s, %s, %s) | ||
""", | ||
( | ||
puzzle_id, | ||
team_name, | ||
submission_time, | ||
submission_answer, | ||
submission_is_correct, | ||
), | ||
) | ||
|
||
|
||
async def find_submissions_by_player_id(player_id: str): | ||
async with await psycopg.AsyncConnection.connect(DATABASE_URL) as aconn: | ||
async with aconn.cursor(row_factory=class_row(Submission)) as acur: | ||
await acur.execute( | ||
""" | ||
SELECT s.puzzle_id, s.team_name, s.submission_time, s.submission_answer, s.submission_is_correct | ||
FROM public.submissions AS s | ||
INNER JOIN public.teams AS t ON t.team_name = s.team_name | ||
INNER JOIN public.players AS p ON p.team_name = t.team_name | ||
WHERE p.discord_id = %s | ||
""", | ||
(player_id,), | ||
) | ||
|
||
return await acur.fetchall() | ||
|
||
|
||
async def find_submissions_by_team(team_name: str): | ||
async with await psycopg.AsyncConnection.connect(DATABASE_URL) as aconn: | ||
async with aconn.cursor(row_factory=class_row(Submission)) as acur: | ||
await acur.execute( | ||
"SELECT * FROM public.submissions WHERE team_name = %s", | ||
(team_name,), | ||
) | ||
|
||
return await acur.fetchall() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import discord | ||
|
||
from functools import wraps | ||
from typing import Coroutine | ||
|
||
|
||
def in_team_channel(func: Coroutine) -> Coroutine: | ||
@wraps(func) | ||
async def wrapper(self, interaction: discord.Interaction): | ||
user = interaction.user | ||
if not isinstance(user, discord.Member): | ||
return await interaction.response.send_message("Something went wrong!") | ||
channel = interaction.channel | ||
# TODO: check if user is in team channel | ||
is_in_team_channel = True | ||
|
||
return await func(self, interaction) | ||
|
||
return wrapper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import os | ||
import sys | ||
|
||
# Get the absolute path to the project root directory | ||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||
|
||
# Add the project root to the Python path | ||
sys.path.insert(0, PROJECT_ROOT) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import pytest | ||
import pytest_asyncio | ||
|
||
from tests.utils import truncate | ||
from src.queries.puzzle import create_puzzle, find_puzzle | ||
from src.models.puzzle import Puzzle | ||
|
||
|
||
class TestClass: | ||
@pytest_asyncio.fixture(autouse=True) | ||
async def async_setup(self): | ||
await truncate() | ||
yield | ||
|
||
@pytest.mark.asyncio | ||
async def test_can_create_puzzle(self): | ||
expected: Puzzle = Puzzle( | ||
"UTS-1", "The Answer of Life", "42", "Skelly", "tiny.cc/rickroll", "UTS" | ||
) | ||
await create_puzzle( | ||
expected.puzzle_id, | ||
expected.puzzle_name, | ||
expected.puzzle_answer, | ||
expected.puzzle_author, | ||
expected.puzzle_link, | ||
expected.uni, | ||
) | ||
result = await find_puzzle("UTS-1") | ||
assert expected == result |
Oops, something went wrong.