2025-02-18 08:04:02 +01:00
|
|
|
import logging
|
2025-02-16 22:36:18 +01:00
|
|
|
from dataclasses import dataclass
|
2025-02-18 08:04:02 +01:00
|
|
|
from datetime import datetime, timezone
|
|
|
|
from logging import Logger
|
|
|
|
from typing import Any, Dict, Optional
|
2025-02-16 22:36:18 +01:00
|
|
|
|
|
|
|
from bson import ObjectId
|
2025-02-18 08:04:02 +01:00
|
|
|
from pymongo.results import InsertOneResult
|
|
|
|
|
|
|
|
from classes.errors import WalletNotFoundError
|
2025-02-18 20:25:57 +01:00
|
|
|
from classes.errors.wallet import WalletInsufficientFunds
|
2025-02-18 08:04:02 +01:00
|
|
|
from modules.database import col_wallets
|
|
|
|
|
|
|
|
logger: Logger = logging.getLogger(__name__)
|
2025-02-16 22:36:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Wallet:
|
|
|
|
_id: ObjectId
|
|
|
|
owner_id: int
|
|
|
|
guild_id: int
|
2025-02-18 08:04:02 +01:00
|
|
|
balance: float
|
2025-02-16 22:36:18 +01:00
|
|
|
is_frozen: bool
|
|
|
|
created: datetime
|
2025-02-18 08:04:02 +01:00
|
|
|
|
|
|
|
# TODO Write a docstring
|
|
|
|
@classmethod
|
|
|
|
async def from_id(
|
2025-02-18 20:25:57 +01:00
|
|
|
cls, owner_id: int, guild_id: int, allow_creation: bool = True
|
2025-02-18 08:04:02 +01:00
|
|
|
) -> "Wallet":
|
|
|
|
db_entry = await col_wallets.find_one(
|
|
|
|
{"owner_id": owner_id, "guild_id": guild_id}
|
|
|
|
)
|
|
|
|
|
|
|
|
if db_entry is None:
|
|
|
|
if not allow_creation:
|
|
|
|
raise WalletNotFoundError(owner_id, guild_id)
|
|
|
|
|
|
|
|
db_entry = Wallet.get_defaults(owner_id, guild_id)
|
|
|
|
|
|
|
|
insert_result: InsertOneResult = await col_wallets.insert_one(db_entry)
|
|
|
|
|
|
|
|
db_entry["_id"] = insert_result.inserted_id
|
|
|
|
|
|
|
|
return cls(**db_entry)
|
|
|
|
|
|
|
|
def _to_dict(self) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
"_id": self._id,
|
|
|
|
"owner_id": self.owner_id,
|
|
|
|
"guild_id": self.guild_id,
|
|
|
|
"balance": self.balance,
|
|
|
|
"is_frozen": self.is_frozen,
|
|
|
|
"created": self.created,
|
|
|
|
}
|
|
|
|
|
|
|
|
async def _set(self, key: str, value: Any) -> None:
|
|
|
|
if not hasattr(self, key):
|
|
|
|
raise AttributeError()
|
|
|
|
|
|
|
|
setattr(self, key, value)
|
|
|
|
|
|
|
|
await col_wallets.update_one(
|
|
|
|
{"_id": self._id}, {"$set": {key: value}}, upsert=True
|
|
|
|
)
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
"Set attribute '%s' of the wallet %s to '%s'", key, str(self._id), value
|
|
|
|
)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_defaults(
|
2025-02-18 20:25:57 +01:00
|
|
|
owner_id: Optional[int] = None, guild_id: Optional[int] = None
|
2025-02-18 08:04:02 +01:00
|
|
|
) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
"owner_id": owner_id,
|
|
|
|
"guild_id": guild_id,
|
|
|
|
"balance": 0.0,
|
|
|
|
"is_frozen": False,
|
|
|
|
"created": datetime.now(tz=timezone.utc),
|
|
|
|
}
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_default_value(key: str) -> Any:
|
|
|
|
if key not in Wallet.get_defaults():
|
|
|
|
raise KeyError(f"There's no default value for key '{key}' in Wallet")
|
|
|
|
|
|
|
|
return Wallet.get_defaults()[key]
|
|
|
|
|
|
|
|
# TODO Write a docstring
|
|
|
|
async def freeze(self) -> None:
|
|
|
|
await self._set("is_frozen", True)
|
|
|
|
|
|
|
|
# TODO Write a docstring
|
|
|
|
async def unfreeze(self) -> None:
|
|
|
|
await self._set("is_frozen", False)
|
|
|
|
|
2025-02-18 20:25:57 +01:00
|
|
|
# TODO Write a docstring
|
2025-02-18 08:04:02 +01:00
|
|
|
async def deposit(self, amount: float) -> None:
|
|
|
|
await self._set("balance", round(self.balance + amount, 2))
|
|
|
|
|
2025-02-18 20:25:57 +01:00
|
|
|
# TODO Write a docstring
|
|
|
|
async def withdraw(
|
|
|
|
self,
|
|
|
|
amount: float,
|
|
|
|
allow_overdraft: bool = False,
|
|
|
|
overdraft_limit: Optional[float] = None,
|
|
|
|
) -> None:
|
|
|
|
if amount > self.balance and (
|
|
|
|
not allow_overdraft or (allow_overdraft and amount > overdraft_limit)
|
|
|
|
):
|
|
|
|
raise WalletInsufficientFunds(self, amount)
|
|
|
|
|
2025-02-18 08:04:02 +01:00
|
|
|
await self._set("balance", round(self.balance - amount, 2))
|