WIP: Guilds and Wallets

This commit is contained in:
Profitroll 2025-02-20 22:51:01 +01:00
parent 65b0e30c75
commit bf6ca24eed
Signed by: profitroll
GPG Key ID: FA35CAB49DACD3B2
12 changed files with 181 additions and 14 deletions

40
api/extensions/guild.py Normal file
View File

@ -0,0 +1,40 @@
import logging
from logging import Logger
from fastapi import HTTPException, status
from fastapi.responses import JSONResponse
from api.app import app
from classes import PycordGuild
from classes.errors import WalletNotFoundError, GuildNotFoundError
from classes.wallet import Wallet
logger: Logger = logging.getLogger(__name__)
@app.get("/v1/guilds/{guild_id}", response_class=JSONResponse)
async def get_guild_wallet(guild_id: int):
try:
guild: PycordGuild = await PycordGuild.from_id(guild_id, allow_creation=False)
except GuildNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Guild not found"
) from exc
except NotImplementedError as exc:
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
) from exc
return guild.to_dict(json_compatible=True)
@app.get("/v1/guilds/{guild_id}/wallets/{user_id}", response_class=JSONResponse)
async def get_guild_wallet(guild_id: int, user_id: int):
try:
wallet: Wallet = await Wallet.from_id(user_id, guild_id, allow_creation=False)
except WalletNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Wallet not found"
) from exc
return wallet.to_dict(json_compatible=True)

23
api/extensions/user.py Normal file
View File

@ -0,0 +1,23 @@
import logging
from logging import Logger
from fastapi import HTTPException, status
from fastapi.responses import JSONResponse
from api.app import app
from classes import PycordUser
from classes.errors import UserNotFoundError
logger: Logger = logging.getLogger(__name__)
@app.get("/v1/users/{user_id}", response_class=JSONResponse)
async def get_user(user_id: int):
try:
user: PycordUser = await PycordUser.from_id(user_id, allow_creation=False)
except UserNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
) from exc
return user.to_dict(json_compatible=True)

View File

@ -1,9 +1,13 @@
import logging
from logging import Logger
from pathlib import Path from pathlib import Path
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from api.app import app from api.app import app
logger: Logger = logging.getLogger(__name__)
@app.get("/favicon.ico", response_class=FileResponse, include_in_schema=False) @app.get("/favicon.ico", response_class=FileResponse, include_in_schema=False)
async def favicon(): async def favicon():

View File

@ -1,4 +1,5 @@
from .pycord_guild import PycordGuild from .pycord_guild import PycordGuild
from .pycord_guild_colors import PycordGuildColors from .pycord_guild_colors import PycordGuildColors
from .pycord_user import PycordUser from .pycord_user import PycordUser
from .wallet import Wallet
# from .wallet import Wallet

View File

@ -1,3 +1,4 @@
from .pycord_guild import GuildNotFoundError
from .pycord_user import UserNotFoundError from .pycord_user import UserNotFoundError
from .wallet import ( from .wallet import (
WalletBalanceLimitExceeded, WalletBalanceLimitExceeded,

View File

@ -0,0 +1,7 @@
class GuildNotFoundError(Exception):
"""PycordGuild could not find guild with such an ID in the database"""
def __init__(self, guild_id: int) -> None:
self.guild_id = guild_id
super().__init__(f"Guild with id {self.guild_id} was not found")

View File

@ -1,6 +1,8 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Any, Optional
from bson import ObjectId from bson import ObjectId
from libbot.cache.classes import Cache
@dataclass @dataclass
@ -10,3 +12,36 @@ class PycordGuild:
def __init__(self) -> None: def __init__(self) -> None:
raise NotImplementedError() raise NotImplementedError()
@classmethod
async def from_id(
cls, guild_id: int, allow_creation: bool = True, cache: Optional[Cache] = None
) -> "PycordGuild":
"""Find guild in database and create new record if guild does not exist.
Args:
guild_id (int): User's Discord ID
allow_creation (:obj:`bool`, optional): Create new guild record if none found in the database
cache (:obj:`Cache`, optional): Cache engine to get the cache from
Returns:
PycordGuild: User object
Raises:
GuildNotFoundError: User was not found and creation was not allowed
"""
raise NotImplementedError()
def to_dict(self, json_compatible: bool = False) -> Dict[str, Any]:
"""Convert PycordGuild object to a JSON representation.
Args:
json_compatible (bool): Whether the JSON-incompatible objects like ObjectId need to be converted
Returns:
Dict[str, Any]: JSON representation of PycordGuild
"""
return {
"_id": self._id if not json_compatible else str(self._id),
"id": self.id,
}

View File

@ -7,8 +7,8 @@ from bson import ObjectId
from libbot.cache.classes import Cache from libbot.cache.classes import Cache
from pymongo.results import InsertOneResult from pymongo.results import InsertOneResult
from classes import Wallet
from classes.errors.pycord_user import UserNotFoundError from classes.errors.pycord_user import UserNotFoundError
from classes.wallet import Wallet
from modules.database import col_users from modules.database import col_users
logger: Logger = logging.getLogger(__name__) logger: Logger = logging.getLogger(__name__)
@ -63,9 +63,17 @@ class PycordUser:
return cls(**db_entry) return cls(**db_entry)
def _to_dict(self) -> Dict[str, Any]: def to_dict(self, json_compatible: bool = False) -> Dict[str, Any]:
"""Convert PycordUser object to a JSON representation.
Args:
json_compatible (bool): Whether the JSON-incompatible objects like ObjectId need to be converted
Returns:
Dict[str, Any]: JSON representation of PycordUser
"""
return { return {
"_id": self._id, "_id": self._id if not json_compatible else str(self._id),
"id": self.id, "id": self.id,
} }
@ -117,7 +125,7 @@ class PycordUser:
if cache is None: if cache is None:
return return
user_dict: Dict[str, Any] = self._to_dict() user_dict: Dict[str, Any] = self.to_dict()
if user_dict is not None: if user_dict is not None:
cache.set_json(self._get_cache_key(), user_dict) cache.set_json(self._get_cache_key(), user_dict)

View File

@ -7,12 +7,12 @@ from typing import Any, Dict, Optional
from bson import ObjectId from bson import ObjectId
from pymongo.results import InsertOneResult from pymongo.results import InsertOneResult
from classes.errors import ( from classes.errors.wallet import (
WalletBalanceLimitExceeded, WalletBalanceLimitExceeded,
WalletNotFoundError, WalletNotFoundError,
WalletOverdraftLimitExceeded, WalletOverdraftLimitExceeded,
WalletInsufficientFunds,
) )
from classes.errors.wallet import WalletInsufficientFunds
from modules.database import col_wallets from modules.database import col_wallets
logger: Logger = logging.getLogger(__name__) logger: Logger = logging.getLogger(__name__)
@ -46,9 +46,17 @@ class Wallet:
return cls(**db_entry) return cls(**db_entry)
def _to_dict(self) -> Dict[str, Any]: def to_dict(self, json_compatible: bool = False) -> Dict[str, Any]:
"""Convert Wallet object to a JSON representation.
Args:
json_compatible (bool): Whether the JSON-incompatible objects like ObjectId need to be converted
Returns:
Dict[str, Any]: JSON representation of Wallet
"""
return { return {
"_id": self._id, "_id": self._id if not json_compatible else str(self._id),
"owner_id": self.owner_id, "owner_id": self.owner_id,
"guild_id": self.guild_id, "guild_id": self.guild_id,
"balance": self.balance, "balance": self.balance,

27
cli.py Normal file
View File

@ -0,0 +1,27 @@
import logging
from argparse import ArgumentParser
from logging import Logger
from modules.migrator import migrate_database
logger: Logger = logging.getLogger(__name__)
parser = ArgumentParser(
prog="Javelina",
description="Discord bot for community management.",
)
parser.add_argument("--migrate", action="store_true")
parser.add_argument("--only-api", action="store_true")
args = parser.parse_args()
def main():
if args.migrate:
logger.info("Performing migrations...")
migrate_database()
if __name__ == "__main__":
main()

5
cogs/wallet.py Normal file
View File

@ -0,0 +1,5 @@
from classes.pycord_bot import PycordBot
def setup(client: PycordBot) -> None:
pass

18
main.py
View File

@ -1,23 +1,31 @@
import asyncio import asyncio
import contextlib
import logging import logging
from logging import Logger
from os import getpid from os import getpid
from libbot.utils import config_get from libbot.utils import config_get
# Import required for uvicorn
from api.app import app # noqa
from classes.pycord_bot import PycordBot from classes.pycord_bot import PycordBot
from modules.extensions_loader import dynamic_import_from_src from modules.extensions_loader import dynamic_import_from_src
from modules.scheduler import scheduler from modules.scheduler import scheduler
# Import required for uvicorn
from api.app import app
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG if config_get("debug") else logging.INFO, level=logging.DEBUG if config_get("debug") else logging.INFO,
format="%(name)s.%(funcName)s | %(levelname)s | %(message)s", format="%(name)s.%(funcName)s | %(levelname)s | %(message)s",
datefmt="[%X]", datefmt="[%X]",
) )
logger = logging.getLogger(__name__) logger: Logger = logging.getLogger(__name__)
# Try to import the module that improves performance
# and ignore errors when module is not installed
with contextlib.suppress(ImportError):
import uvloop
uvloop.install()
async def main(): async def main():
@ -26,7 +34,7 @@ async def main():
bot.load_extension("cogs") bot.load_extension("cogs")
# Import API modules # Import API modules
dynamic_import_from_src("api.extensions", star_import=True) dynamic_import_from_src("api/extensions", star_import=True)
try: try:
await bot.start(config_get("bot_token", "bot")) await bot.start(config_get("bot_token", "bot"))