feat(core): implement domain dataclasses for issue #5 #19
@@ -1,53 +1,63 @@
|
|||||||
"""Domain dataclasses for JIGAIDO bounty tracker."""
|
"""Domain dataclasses for JIGAIDO bounty tracker."""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Bounty:
|
class Bounty:
|
||||||
"""Bounty - used for both group and personal bounties.
|
"""A bounty created by a user.
|
||||||
|
|
||||||
Use created_by_user_id to distinguish: if set, it's a group bounty
|
The created_by_user_id field always refers to the user who created the bounty.
|
||||||
created by that user. If None, it's a personal/DM bounty.
|
It does NOT indicate whether the bounty is a group or personal bounty.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
text: Optional[str]
|
text: str | None
|
||||||
link: Optional[str]
|
link: str | None
|
||||||
due_date_ts: Optional[int]
|
due_date_ts: int | None
|
||||||
created_at: int
|
created_at: int
|
||||||
created_by_user_id: Optional[int] = None
|
created_by_user_id: int
|
||||||
|
|
||||||
|
han marked this conversation as resolved
Outdated
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
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?
|
|||||||
class TrackedBounty:
|
class TrackedBounty:
|
||||||
"""A bounty that a user is tracking."""
|
"""A bounty that a user is tracking.
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
we should also add room_id in this trackedbounty to know where the user is tracking the bounty from. to show their list track bounties depends on the room we should also add room_id in this trackedbounty to know where the user is tracking the bounty from. to show their list track bounties depends on the room
|
|||||||
|
|
||||||
|
Use TrackedBounty when you need to record that a user is tracking a specific
|
||||||
|
bounty from a specific room. The room_id indicates which room the bounty
|
||||||
|
was tracked from (useful when users can track bounties across multiple rooms).
|
||||||
|
|
||||||
|
For simple tracking lists, just use bounty_id and created_at.
|
||||||
|
"""
|
||||||
|
|
||||||
bounty_id: int
|
bounty_id: int
|
||||||
created_at: int
|
created_at: int
|
||||||
|
room_id: int
|
||||||
|
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
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RoomData:
|
class RoomData:
|
||||||
"""All data for a room (group or DM).
|
"""All data for a room (group or DM).
|
||||||
|
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
|
|||||||
|
|
||||||
For groups: is_group=True, room_id is negative (Telegram group ID)
|
The room_id can be negative for Telegram groups or positive for DMs.
|
||||||
For DMs: is_group=False, room_id is the user_id (positive)
|
The next_id field is used to generate unique bounty IDs within this room.
|
||||||
next_id is used to generate unique bounty IDs within this room.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
room_id: int
|
room_id: int
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
what is this next_id refers to? what is this next_id refers to?
|
|||||||
is_group: bool
|
|
||||||
bounties: list[Bounty]
|
bounties: list[Bounty]
|
||||||
|
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?
|
|||||||
next_id: int
|
next_id: int
|
||||||
|
|
||||||
|
|
||||||
|
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?
|
|||||||
@dataclass
|
@dataclass
|
||||||
class TrackingData:
|
class TrackingData:
|
||||||
"""User tracking data within a group.
|
"""User tracking state within a group.
|
||||||
|
|
||||||
group_id is the Telegram group ID (always negative).
|
TrackingData vs TrackedBounty:
|
||||||
|
- Use TrackingData to store ALL tracked bounties for a user in a specific group.
|
||||||
|
It contains the group_id, user_id, and a list of TrackedBounty entries.
|
||||||
|
- Use TrackedBounty to represent a single tracked bounty entry within that list.
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
is it possible to combine both UserData and GroupData? since their attributes are similar as well. we can name it maybe like RoomData? it sounds too specific to telegram, but that is the bigger picture. so we used the room_id. and makes sure it can be minus as well because telegram's id for group has minus in it. if we can't do that maybe we can add attribute like is this group or personal. is that makes sense? is it possible to combine both UserData and GroupData? since their attributes are similar as well. we can name it maybe like RoomData? it sounds too specific to telegram, but that is the bigger picture. so we used the room_id. and makes sure it can be minus as well because telegram's id for group has minus in it. if we can't do that maybe we can add attribute like is this group or personal. is that makes sense?
|
|||||||
|
|
||||||
|
TrackingData is the container, TrackedBounty is the item.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
group_id: int
|
group_id: int
|
||||||
|
han marked this conversation as resolved
Outdated
han
commented
I think we should rename it into room_id to make it works and makes sense for both group and DM I think we should rename it into room_id to make it works and makes sense for both group and DM
|
|||||||
|
|||||||
@@ -20,14 +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,
|
created_by_user_id=123,
|
||||||
)
|
)
|
||||||
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
|
assert b.created_by_user_id == 123
|
||||||
|
|
||||||
def test_bounty_optional_fields_can_be_none(self):
|
def test_bounty_optional_fields_can_be_none(self):
|
||||||
b = Bounty(
|
b = Bounty(
|
||||||
@@ -36,7 +36,7 @@ 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,
|
created_by_user_id=123,
|
||||||
)
|
)
|
||||||
assert b.text is None
|
assert b.text is None
|
||||||
assert b.link is None
|
assert b.link is None
|
||||||
@@ -49,7 +49,7 @@ 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,
|
created_by_user_id=123,
|
||||||
)
|
)
|
||||||
b2 = Bounty(
|
b2 = Bounty(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -57,7 +57,7 @@ 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,
|
created_by_user_id=123,
|
||||||
)
|
)
|
||||||
assert b1 == b2
|
assert b1 == b2
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ 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,
|
created_by_user_id=123,
|
||||||
)
|
)
|
||||||
b2 = Bounty(
|
b2 = Bounty(
|
||||||
id=2,
|
id=2,
|
||||||
@@ -76,31 +76,21 @@ 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,
|
created_by_user_id=456,
|
||||||
)
|
)
|
||||||
assert b1 != b2
|
assert b1 != b2
|
||||||
|
|
||||||
def test_bounty_with_created_by_user_id(self):
|
|
||||||
b = Bounty(
|
|
||||||
id=1,
|
|
||||||
text="Group task",
|
|
||||||
link=None,
|
|
||||||
due_date_ts=None,
|
|
||||||
created_at=0,
|
|
||||||
created_by_user_id=123456,
|
|
||||||
)
|
|
||||||
assert b.created_by_user_id == 123456
|
|
||||||
|
|
||||||
|
|
||||||
class TestTrackedBounty:
|
class TestTrackedBounty:
|
||||||
def test_create_tracked_bounty(self):
|
def test_create_tracked_bounty(self):
|
||||||
tb = TrackedBounty(bounty_id=5, created_at=1735600000)
|
tb = TrackedBounty(bounty_id=5, created_at=1735600000, room_id=-1001)
|
||||||
assert tb.bounty_id == 5
|
assert tb.bounty_id == 5
|
||||||
assert tb.created_at == 1735600000
|
assert tb.created_at == 1735600000
|
||||||
|
assert tb.room_id == -1001
|
||||||
|
|
||||||
def test_tracked_bounty_comparison(self):
|
def test_tracked_bounty_comparison(self):
|
||||||
tb1 = TrackedBounty(bounty_id=1, created_at=0)
|
tb1 = TrackedBounty(bounty_id=1, created_at=0, room_id=-1001)
|
||||||
tb2 = TrackedBounty(bounty_id=1, created_at=0)
|
tb2 = TrackedBounty(bounty_id=1, created_at=0, room_id=-1001)
|
||||||
assert tb1 == tb2
|
assert tb1 == tb2
|
||||||
|
|
||||||
|
|
||||||
@@ -108,24 +98,20 @@ class TestRoomData:
|
|||||||
def test_create_group_room_data(self):
|
def test_create_group_room_data(self):
|
||||||
rd = RoomData(
|
rd = RoomData(
|
||||||
room_id=-1001,
|
room_id=-1001,
|
||||||
is_group=True,
|
|
||||||
bounties=[],
|
bounties=[],
|
||||||
next_id=1,
|
next_id=1,
|
||||||
)
|
)
|
||||||
assert rd.room_id == -1001
|
assert rd.room_id == -1001
|
||||||
assert rd.is_group is True
|
|
||||||
assert rd.bounties == []
|
assert rd.bounties == []
|
||||||
assert rd.next_id == 1
|
assert rd.next_id == 1
|
||||||
|
|
||||||
def test_create_dm_room_data(self):
|
def test_create_dm_room_data(self):
|
||||||
rd = RoomData(
|
rd = RoomData(
|
||||||
room_id=123456,
|
room_id=123456,
|
||||||
is_group=False,
|
|
||||||
bounties=[],
|
bounties=[],
|
||||||
next_id=1,
|
next_id=1,
|
||||||
)
|
)
|
||||||
assert rd.room_id == 123456
|
assert rd.room_id == 123456
|
||||||
assert rd.is_group is False
|
|
||||||
assert rd.bounties == []
|
assert rd.bounties == []
|
||||||
assert rd.next_id == 1
|
assert rd.next_id == 1
|
||||||
|
|
||||||
@@ -138,7 +124,7 @@ class TestRoomData:
|
|||||||
created_at=0,
|
created_at=0,
|
||||||
created_by_user_id=123,
|
created_by_user_id=123,
|
||||||
)
|
)
|
||||||
rd = RoomData(room_id=-1001, is_group=True, bounties=[b], next_id=2)
|
rd = RoomData(room_id=-1001, bounties=[b], next_id=2)
|
||||||
assert len(rd.bounties) == 1
|
assert len(rd.bounties) == 1
|
||||||
assert rd.bounties[0].text == "Task"
|
assert rd.bounties[0].text == "Task"
|
||||||
assert rd.bounties[0].created_by_user_id == 123
|
assert rd.bounties[0].created_by_user_id == 123
|
||||||
@@ -152,7 +138,7 @@ class TestTrackingData:
|
|||||||
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, room_id=-1001)
|
||||||
td = TrackingData(group_id=-1001, 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
|
||||||
|
|||||||
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