Skip to content

Commit ab7b890

Browse files
committed
feat: add AttackDefProblem model
1 parent d23dcfb commit ab7b890

File tree

5 files changed

+172
-0
lines changed

5 files changed

+172
-0
lines changed

src/pwncore/models/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
Team_Pydantic,
2525
User_Pydantic,
2626
)
27+
from pwncore.models.round2 import (
28+
AttackDefProblem,
29+
)
2730
from pwncore.models.pre_event import (
2831
PreEventProblem,
2932
PreEventSolvedProblem,
@@ -51,6 +54,7 @@
5154
"PreEventProblem_Pydantic",
5255
"Problem_Pydantic",
5356
"BaseProblem",
57+
"AttackDefProblem",
5458
)
5559

5660

src/pwncore/models/powerups.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from __future__ import annotations
2+
from datetime import timedelta
3+
from dataclasses import dataclass
4+
from typing import ClassVar
5+
from pydantic import field_serializer
6+
7+
from tortoise import fields
8+
from tortoise.contrib.pydantic.creator import pydantic_model_creator
9+
from tortoise.models import Model
10+
11+
from pwncore.models import (
12+
AttackDefTeam
13+
)
14+
15+
__all__ = ("PowerupType", "UsedPowerup", "Powerup", "Powerup_Pydantic")
16+
17+
@dataclass
18+
class PowerupType:
19+
name: str
20+
cost: int
21+
max_uses_default: int = 1
22+
duration: timedelta | None = None
23+
all_powerup_types: ClassVar[dict[str, PowerupType]] = {}
24+
25+
def __post_init__(self):
26+
self.all_powerup_types[self.name] = self
27+
28+
class PowerupTypeField(fields.CharField):
29+
def __init__(self, **kwargs):
30+
super().__init__(128, **kwargs)
31+
# if not issubclass(enum_type, Enum):
32+
# raise ConfigurationError("{} is not a subclass of Enum!".format(enum_type))
33+
# self._powerup_type = powerup_type
34+
35+
def to_db_value(self, powerup_type: PowerupType, instance) -> str:
36+
return powerup_type.name
37+
38+
def to_python_value(self, value: PowerupType) -> PowerupType:
39+
return value
40+
41+
Sabotage = PowerupType(name = "Sabotage", cost = 500, duration = timedelta(seconds=5))
42+
Shield = PowerupType(name = "Shield", cost = 200, max_uses_default = 3, duration = timedelta(seconds=10))
43+
PointSiphon = PowerupType(name = "PointSiphon", cost = 150, duration = timedelta(seconds=15))
44+
Upgrade = PowerupType(name = "Upgrade", cost = 100)
45+
46+
class Powerup(Model):
47+
id = fields.IntField(pk=True)
48+
powerup_type = PowerupTypeField()
49+
attack_def_team: fields.ForeignKeyRelation[AttackDefTeam] = fields.ForeignKeyField(
50+
"models.AttackDefTeam", related_name="powerups"
51+
)
52+
created_at = fields.DatetimeField(auto_now_add=True)
53+
used_at_least_once = fields.BooleanField(default=False)
54+
max_uses = fields.IntField(default=1)
55+
used_count = fields.IntField(default=0)
56+
used_instance = fields.ReverseRelation["UsedPowerup"]
57+
58+
async def save(self, *args, **kwargs):
59+
if not self.max_uses:
60+
self.max_uses = all_powerup_types[self.powerup_type].max_uses_default
61+
await super().save(*args, **kwargs)
62+
63+
class UsedPowerup(Model):
64+
id = fields.IntField(pk=True)
65+
powerup: fields.ForeignKeyRelation[Powerup] = fields.ForeignKeyField(
66+
"models.Powerup", related_name="used_instances", null=False
67+
)
68+
used_at = fields.DatetimeField(auto_now_add=True)
69+
70+
Powerup_Pydantic = pydantic_model_creator(Powerup)

src/pwncore/models/round2.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from __future__ import annotations
2+
from tortoise import fields
3+
from tortoise.models import Model
4+
__all__ = (
5+
"AttackDefProblem",
6+
"Problem",
7+
)
8+
#TODO: Actually implement this.
9+
# For now this is a dummy class for testing AttackDefTeam.
10+
class AttackDefProblem(Model):
11+
problem: fields.OneToOneRelation[Problem] = fields.OneToOneField(
12+
"models.Problem"
13+
)
14+
attack_def_team: fields.ForeignKeyRelation[AttackDefTeam] = fields.ForeignKeyField(
15+
"models.AttackDefTeam", related_name="assigned_attack_def_problem"
16+
)

src/pwncore/routes/admin.py

+15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
Problem,
1515
Team,
1616
User,
17+
AttackDefTeam,
18+
AttackDefProblem,
1719
)
1820
from pwncore.models.ctf import SolvedProblem
1921
from pwncore.models.pre_event import PreEventUser
@@ -150,6 +152,19 @@ async def init_db(
150152
await Team.create(
151153
name="Triple A battery", secret_hash=bcrypt.hash("chotiwali"), coins=20
152154
)
155+
triple_b_battery = await Team.create(
156+
name="Triple B battery", secret_hash=bcrypt.hash("chotiwali2"), coins=20
157+
)
158+
await AttackDefTeam.create(
159+
team_id=(await Team.get(name="Triple A battery")).id
160+
)
161+
await AttackDefTeam.create(
162+
team_id=(await Team.get(name="Triple B battery")).id
163+
)
164+
await AttackDefProblem.create(
165+
problem_id=(await Problem.get(name="GitGood2")).id,
166+
attack_def_team_id=(await AttackDefTeam.get(team_id=triple_b_battery.id)).id
167+
)
153168
await PreEventUser.create(tag="23BCE1000", email="[email protected]")
154169
await PreEventUser.create(tag="23BRS1000", email="[email protected]")
155170
await PreEventSolvedProblem.create(user_id="23BCE1000", problem_id="1")

src/pwncore/routes/powerups.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
2+
from tortoise.contrib.pydantic.creator import pydantic_model_creator
3+
from fastapi import APIRouter, HTTPException, Response
4+
5+
from pwncore.config import config
6+
from pwncore.models import (
7+
Powerup,
8+
PowerupType,
9+
Team,
10+
AttackDefTeam,
11+
UsedPowerup,
12+
Powerup_Pydantic,
13+
)
14+
from pwncore.routes.auth import RequireJwt
15+
16+
metadata = {"name": "powerups", "description": "Powerups for teams"}
17+
router = APIRouter(prefix="/powerups", tags=["team"])
18+
19+
@router.get("/remaining")
20+
async def view_remaining_powerups(jwt: RequireJwt, response: Response):
21+
team_id = jwt["team_id"]
22+
attack_def_team = await AttackDefTeam.get(team_id=team_id)
23+
if (attack_def_team is None):
24+
return {"msg_code": config.msg_codes["attack_def_team_not_found"]}
25+
return await Powerup_Pydantic.from_queryset(await attack_def_team.remaining_powerups())
26+
27+
@router.get("/used")
28+
async def view_used_powerups(jwt: RequireJwt):
29+
team_id = jwt["team_id"]
30+
attack_def_team = await AttackDefTeam.get(team_id=team_id)
31+
if (attack_def_team is None):
32+
return {"msg_code": config.msg_codes["attack_def_team_not_found"]}
33+
34+
used_powerups = UsedPowerup.filter(powerup__attack_def_team=attack_def_team.id).prefetch_related("powerup")
35+
return await used_powerups
36+
37+
38+
# @router.post("/use/{powerup_type}")
39+
# async def use_powerup(powerup_type: PowerupType, jwt: RequireJwt):
40+
# Define logic to use available powerups and deduct points
41+
# pass
42+
43+
44+
# @router.get("/about/{powerup_type}")
45+
# async def get_about_powerups(powerup_type: PowerupType, jwt: RequireJwt):
46+
# Return the about of the powerup
47+
# pass
48+
49+
50+
# async def lucky_draw(team: Team):
51+
# # Lucky draw logic
52+
# pass
53+
54+
55+
# async def sabotage(team: Team):
56+
# # Sabotage logic
57+
# pass
58+
59+
60+
# async def gamble(team: Team):
61+
# # Gamble logic
62+
# pass
63+
64+
65+
# async def point_siphon(team: Team):
66+
# # Point Siphon logic
67+
# pass

0 commit comments

Comments
 (0)