feat(core): implement domain dataclasses for issue #5 #19
@@ -2,20 +2,14 @@
|
|||||||
|
|
||||||
from core.models import (
|
from core.models import (
|
||||||
Bounty,
|
Bounty,
|
||||||
GroupBounty,
|
|
||||||
PersonalBounty,
|
|
||||||
TrackedBounty,
|
TrackedBounty,
|
||||||
GroupData,
|
RoomData,
|
||||||
TrackingData,
|
TrackingData,
|
||||||
UserData,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Bounty",
|
"Bounty",
|
||||||
"GroupBounty",
|
|
||||||
"PersonalBounty",
|
|
||||||
"TrackedBounty",
|
"TrackedBounty",
|
||||||
"GroupData",
|
"RoomData",
|
||||||
"TrackingData",
|
"TrackingData",
|
||||||
"UserData",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,27 +6,18 @@ from typing import Optional
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Bounty:
|
class Bounty:
|
||||||
"""Base bounty with common fields."""
|
"""Bounty - used for both group and personal bounties.
|
||||||
|
|
||||||
|
Use created_by_user_id to distinguish: if set, it's a group bounty
|
||||||
|
created by that user. If None, it's a personal/DM bounty.
|
||||||
|
"""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
text: Optional[str]
|
text: Optional[str]
|
||||||
link: Optional[str]
|
link: Optional[str]
|
||||||
due_date_ts: Optional[int]
|
due_date_ts: Optional[int]
|
||||||
created_at: int
|
created_at: int
|
||||||
|
created_by_user_id: Optional[int] = None
|
||||||
|
han marked this conversation as resolved
Outdated
|
|||||||
|
|
||||||
@dataclass
|
|
||||||
class GroupBounty(Bounty):
|
|
||||||
"""Bounty created in a group context."""
|
|
||||||
|
|
||||||
created_by_user_id: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class PersonalBounty(Bounty):
|
|
||||||
"""Bounty created in DM/personal context."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
han marked this conversation as resolved
han
commented
can we simplify the Bounty by not having both GroupBounty and PersonalBounty? can we utilize Bounty by adding optional created_by_user_id so in this case we can use Bounty for Group and Personal Bounty. what do you think? can we simplify the Bounty by not having both GroupBounty and PersonalBounty? can we utilize Bounty by adding optional created_by_user_id so in this case we can use Bounty for Group and Personal Bounty. what do you think?
|
|||||||
@dataclass
|
@dataclass
|
||||||
@@ -38,26 +29,27 @@ class TrackedBounty:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GroupData:
|
class RoomData:
|
||||||
"""All data for a group."""
|
"""All data for a room (group or DM).
|
||||||
|
|
||||||
group_id: int
|
For groups: is_group=True, room_id is negative (Telegram group ID)
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
we can remove room_id from here since TrackedBounty is just a relation, while we store the actual data in the TrackingData we can remove room_id from here since TrackedBounty is just a relation, while we store the actual data in the TrackingData
|
|||||||
bounties: list[GroupBounty]
|
For DMs: is_group=False, room_id is the user_id (positive)
|
||||||
|
next_id is used to generate unique bounty IDs within this room.
|
||||||
|
"""
|
||||||
|
|
||||||
|
room_id: int
|
||||||
|
han marked this conversation as resolved
han
commented
since room_id can be negative in RoomData, I don't think we need is_group anymore. if its negative, its group, that should be clear enough since room_id can be negative in RoomData, I don't think we need is_group anymore. if its negative, its group, that should be clear enough
|
|||||||
|
is_group: bool
|
||||||
|
bounties: list[Bounty]
|
||||||
next_id: int
|
next_id: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
what is this next_id refers to? what is this next_id refers to?
|
|||||||
class TrackingData:
|
class TrackingData:
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
I dont understand the difference of TrackingData and TrackedBounty. when to use which? I dont understand the difference of TrackingData and TrackedBounty. when to use which?
|
|||||||
"""User tracking data within a group."""
|
"""User tracking data within a group.
|
||||||
|
|
||||||
|
group_id is the Telegram group ID (always negative).
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
in the tracking data, why there is no grouip id? in a way how do we know this tracking is for user in which group? in the tracking data, why there is no grouip id? in a way how do we know this tracking is for user in which group?
|
|||||||
|
"""
|
||||||
|
|
||||||
|
group_id: int
|
||||||
user_id: int
|
user_id: int
|
||||||
tracked: list[TrackedBounty]
|
tracked: list[TrackedBounty]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class UserData:
|
|
||||||
"""All personal bounties for a user (DM mode)."""
|
|
||||||
|
|
||||||
user_id: int
|
|
||||||
bounties: list[PersonalBounty]
|
|
||||||
next_id: int
|
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ import pytest
|
|||||||
|
|
||||||
from core.models import (
|
from core.models import (
|
||||||
Bounty,
|
Bounty,
|
||||||
GroupBounty,
|
|
||||||
PersonalBounty,
|
|
||||||
TrackedBounty,
|
TrackedBounty,
|
||||||
GroupData,
|
RoomData,
|
||||||
TrackingData,
|
TrackingData,
|
||||||
UserData,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -23,12 +20,14 @@ class TestBounty:
|
|||||||
link="https://github.com/example/repo/issues/1",
|
link="https://github.com/example/repo/issues/1",
|
||||||
due_date_ts=1735689600,
|
due_date_ts=1735689600,
|
||||||
created_at=1735603200,
|
created_at=1735603200,
|
||||||
|
created_by_user_id=None,
|
||||||
)
|
)
|
||||||
assert b.id == 1
|
assert b.id == 1
|
||||||
assert b.text == "Fix the bug"
|
assert b.text == "Fix the bug"
|
||||||
assert b.link == "https://github.com/example/repo/issues/1"
|
assert b.link == "https://github.com/example/repo/issues/1"
|
||||||
assert b.due_date_ts == 1735689600
|
assert b.due_date_ts == 1735689600
|
||||||
assert b.created_at == 1735603200
|
assert b.created_at == 1735603200
|
||||||
|
assert b.created_by_user_id is None
|
||||||
|
|
||||||
def test_bounty_optional_fields_can_be_none(self):
|
def test_bounty_optional_fields_can_be_none(self):
|
||||||
b = Bounty(
|
b = Bounty(
|
||||||
@@ -37,20 +36,52 @@ class TestBounty:
|
|||||||
link=None,
|
link=None,
|
||||||
due_date_ts=None,
|
due_date_ts=None,
|
||||||
created_at=0,
|
created_at=0,
|
||||||
|
created_by_user_id=None,
|
||||||
)
|
)
|
||||||
assert b.text is None
|
assert b.text is None
|
||||||
assert b.link is None
|
assert b.link is None
|
||||||
assert b.due_date_ts is None
|
assert b.due_date_ts is None
|
||||||
|
|
||||||
def test_bounty_comparison(self):
|
def test_bounty_comparison_equal(self):
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
can we add a test to compare two different bounty with different data as well? can we add a test to compare two different bounty with different data as well?
|
|||||||
b1 = Bounty(id=1, text="a", link=None, due_date_ts=None, created_at=0)
|
b1 = Bounty(
|
||||||
b2 = Bounty(id=1, text="a", link=None, due_date_ts=None, created_at=0)
|
id=1,
|
||||||
|
text="a",
|
||||||
|
link=None,
|
||||||
|
due_date_ts=None,
|
||||||
|
created_at=0,
|
||||||
|
created_by_user_id=None,
|
||||||
|
)
|
||||||
|
b2 = Bounty(
|
||||||
|
id=1,
|
||||||
|
text="a",
|
||||||
|
link=None,
|
||||||
|
due_date_ts=None,
|
||||||
|
created_at=0,
|
||||||
|
created_by_user_id=None,
|
||||||
|
)
|
||||||
assert b1 == b2
|
assert b1 == b2
|
||||||
|
|
||||||
|
def test_bounty_comparison_not_equal(self):
|
||||||
|
b1 = Bounty(
|
||||||
|
id=1,
|
||||||
|
text="a",
|
||||||
|
link=None,
|
||||||
|
due_date_ts=None,
|
||||||
|
created_at=0,
|
||||||
|
created_by_user_id=None,
|
||||||
|
)
|
||||||
|
b2 = Bounty(
|
||||||
|
id=2,
|
||||||
|
text="b",
|
||||||
|
link=None,
|
||||||
|
due_date_ts=None,
|
||||||
|
created_at=0,
|
||||||
|
created_by_user_id=None,
|
||||||
|
)
|
||||||
|
assert b1 != b2
|
||||||
|
|
||||||
class TestGroupBounty:
|
def test_bounty_with_created_by_user_id(self):
|
||||||
def test_create_group_bounty(self):
|
b = Bounty(
|
||||||
gb = GroupBounty(
|
|
||||||
id=1,
|
id=1,
|
||||||
text="Group task",
|
text="Group task",
|
||||||
link=None,
|
link=None,
|
||||||
@@ -58,43 +89,7 @@ class TestGroupBounty:
|
|||||||
created_at=0,
|
created_at=0,
|
||||||
created_by_user_id=123456,
|
created_by_user_id=123456,
|
||||||
)
|
)
|
||||||
assert gb.id == 1
|
assert b.created_by_user_id == 123456
|
||||||
assert gb.text == "Group task"
|
|
||||||
assert gb.created_by_user_id == 123456
|
|
||||||
|
|
||||||
def test_group_bounty_inherits_from_bounty(self):
|
|
||||||
gb = GroupBounty(
|
|
||||||
id=1,
|
|
||||||
text="Task",
|
|
||||||
link="https://example.com",
|
|
||||||
due_date_ts=int(time.time()),
|
|
||||||
created_at=0,
|
|
||||||
created_by_user_id=999,
|
|
||||||
)
|
|
||||||
assert isinstance(gb, Bounty)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPersonalBounty:
|
|
||||||
def test_create_personal_bounty(self):
|
|
||||||
pb = PersonalBounty(
|
|
||||||
id=1,
|
|
||||||
text="Personal task",
|
|
||||||
link=None,
|
|
||||||
due_date_ts=None,
|
|
||||||
created_at=0,
|
|
||||||
)
|
|
||||||
assert pb.id == 1
|
|
||||||
assert pb.text == "Personal task"
|
|
||||||
|
|
||||||
def test_personal_bounty_inherits_from_bounty(self):
|
|
||||||
pb = PersonalBounty(
|
|
||||||
id=1,
|
|
||||||
text="Task",
|
|
||||||
link=None,
|
|
||||||
due_date_ts=None,
|
|
||||||
created_at=0,
|
|
||||||
)
|
|
||||||
assert isinstance(pb, Bounty)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTrackedBounty:
|
class TestTrackedBounty:
|
||||||
@@ -109,19 +104,33 @@ class TestTrackedBounty:
|
|||||||
assert tb1 == tb2
|
assert tb1 == tb2
|
||||||
|
|
||||||
|
|
||||||
class TestGroupData:
|
class TestRoomData:
|
||||||
def test_create_group_data(self):
|
def test_create_group_room_data(self):
|
||||||
gd = GroupData(
|
rd = RoomData(
|
||||||
group_id=-1001,
|
room_id=-1001,
|
||||||
|
is_group=True,
|
||||||
bounties=[],
|
bounties=[],
|
||||||
next_id=1,
|
next_id=1,
|
||||||
)
|
)
|
||||||
assert gd.group_id == -1001
|
assert rd.room_id == -1001
|
||||||
assert gd.bounties == []
|
assert rd.is_group is True
|
||||||
assert gd.next_id == 1
|
assert rd.bounties == []
|
||||||
|
assert rd.next_id == 1
|
||||||
|
|
||||||
def test_group_data_with_bounties(self):
|
def test_create_dm_room_data(self):
|
||||||
gb = GroupBounty(
|
rd = RoomData(
|
||||||
|
room_id=123456,
|
||||||
|
is_group=False,
|
||||||
|
bounties=[],
|
||||||
|
next_id=1,
|
||||||
|
)
|
||||||
|
assert rd.room_id == 123456
|
||||||
|
assert rd.is_group is False
|
||||||
|
assert rd.bounties == []
|
||||||
|
assert rd.next_id == 1
|
||||||
|
|
||||||
|
def test_room_data_with_bounties(self):
|
||||||
|
b = Bounty(
|
||||||
id=1,
|
id=1,
|
||||||
text="Task",
|
text="Task",
|
||||||
link=None,
|
link=None,
|
||||||
@@ -129,39 +138,21 @@ class TestGroupData:
|
|||||||
created_at=0,
|
created_at=0,
|
||||||
created_by_user_id=123,
|
created_by_user_id=123,
|
||||||
)
|
)
|
||||||
gd = GroupData(group_id=-1001, bounties=[gb], next_id=2)
|
rd = RoomData(room_id=-1001, is_group=True, bounties=[b], next_id=2)
|
||||||
assert len(gd.bounties) == 1
|
assert len(rd.bounties) == 1
|
||||||
assert gd.bounties[0].text == "Task"
|
assert rd.bounties[0].text == "Task"
|
||||||
|
assert rd.bounties[0].created_by_user_id == 123
|
||||||
|
|
||||||
|
|
||||||
class TestTrackingData:
|
class TestTrackingData:
|
||||||
def test_create_tracking_data(self):
|
def test_create_tracking_data(self):
|
||||||
td = TrackingData(user_id=123456, tracked=[])
|
td = TrackingData(group_id=-1001, user_id=123456, tracked=[])
|
||||||
|
assert td.group_id == -1001
|
||||||
assert td.user_id == 123456
|
assert td.user_id == 123456
|
||||||
assert td.tracked == []
|
assert td.tracked == []
|
||||||
|
|
||||||
def test_tracking_data_with_tracked(self):
|
def test_tracking_data_with_tracked(self):
|
||||||
tb = TrackedBounty(bounty_id=5, created_at=0)
|
tb = TrackedBounty(bounty_id=5, created_at=0)
|
||||||
td = TrackingData(user_id=123, tracked=[tb])
|
td = TrackingData(group_id=-1001, user_id=123, tracked=[tb])
|
||||||
assert len(td.tracked) == 1
|
assert len(td.tracked) == 1
|
||||||
assert td.tracked[0].bounty_id == 5
|
assert td.tracked[0].bounty_id == 5
|
||||||
|
|
||||||
|
|
||||||
class TestUserData:
|
|
||||||
def test_create_user_data(self):
|
|
||||||
ud = UserData(user_id=123456, bounties=[], next_id=1)
|
|
||||||
assert ud.user_id == 123456
|
|
||||||
assert ud.bounties == []
|
|
||||||
assert ud.next_id == 1
|
|
||||||
|
|
||||||
def test_user_data_with_bounties(self):
|
|
||||||
pb = PersonalBounty(
|
|
||||||
id=1,
|
|
||||||
text="My task",
|
|
||||||
link=None,
|
|
||||||
due_date_ts=None,
|
|
||||||
created_at=0,
|
|
||||||
)
|
|
||||||
ud = UserData(user_id=123, bounties=[pb], next_id=2)
|
|
||||||
assert len(ud.bounties) == 1
|
|
||||||
assert ud.bounties[0].text == "My task"
|
|
||||||
|
|||||||
created_by_user_id by default will refer to the user who created it, so I think its not none and not optional. but created_by_user_id doesnt reflect if its a group or not. Bounty doesn't need to know if its a group or not. because we have list of bounties in the RoomData and that is enough