feat(core): implement domain dataclasses for issue #5 #19
21
core/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Core domain models for JIGAIDO."""
|
||||
|
||||
from core.models import (
|
||||
Bounty,
|
||||
GroupBounty,
|
||||
PersonalBounty,
|
||||
TrackedBounty,
|
||||
GroupData,
|
||||
TrackingData,
|
||||
UserData,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Bounty",
|
||||
"GroupBounty",
|
||||
"PersonalBounty",
|
||||
"TrackedBounty",
|
||||
"GroupData",
|
||||
"TrackingData",
|
||||
"UserData",
|
||||
]
|
||||
63
core/models.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Domain dataclasses for JIGAIDO bounty tracker."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Bounty:
|
||||
"""Base bounty with common fields."""
|
||||
|
||||
id: int
|
||||
text: Optional[str]
|
||||
link: Optional[str]
|
||||
due_date_ts: Optional[int]
|
||||
created_at: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class GroupBounty(Bounty):
|
||||
"""Bounty created in a group context."""
|
||||
|
han marked this conversation as resolved
Outdated
|
||||
|
||||
created_by_user_id: int
|
||||
|
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?
|
||||
|
||||
|
||||
|
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
|
||||
@dataclass
|
||||
class PersonalBounty(Bounty):
|
||||
"""Bounty created in DM/personal context."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackedBounty:
|
||||
"""A bounty that a user is tracking."""
|
||||
|
||||
|
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
|
||||
bounty_id: int
|
||||
created_at: int
|
||||
|
||||
|
||||
@dataclass
|
||||
|
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
|
||||
class GroupData:
|
||||
"""All data for a group."""
|
||||
|
||||
group_id: int
|
||||
bounties: list[GroupBounty]
|
||||
next_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?
|
||||
|
||||
|
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?
|
||||
|
||||
@dataclass
|
||||
class TrackingData:
|
||||
|
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?
|
||||
"""User tracking data within a group."""
|
||||
|
||||
user_id: int
|
||||
tracked: list[TrackedBounty]
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserData:
|
||||
|
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?
|
||||
"""All personal bounties for a user (DM mode)."""
|
||||
|
||||
user_id: int
|
||||
bounties: list[PersonalBounty]
|
||||
next_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
|
||||
167
tests/test_models.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""Tests for core/models.py — domain dataclasses."""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from core.models import (
|
||||
Bounty,
|
||||
GroupBounty,
|
||||
PersonalBounty,
|
||||
TrackedBounty,
|
||||
GroupData,
|
||||
TrackingData,
|
||||
UserData,
|
||||
)
|
||||
|
||||
|
||||
class TestBounty:
|
||||
def test_create_bounty(self):
|
||||
b = Bounty(
|
||||
id=1,
|
||||
text="Fix the bug",
|
||||
link="https://github.com/example/repo/issues/1",
|
||||
due_date_ts=1735689600,
|
||||
created_at=1735603200,
|
||||
)
|
||||
assert b.id == 1
|
||||
assert b.text == "Fix the bug"
|
||||
assert b.link == "https://github.com/example/repo/issues/1"
|
||||
assert b.due_date_ts == 1735689600
|
||||
assert b.created_at == 1735603200
|
||||
|
||||
def test_bounty_optional_fields_can_be_none(self):
|
||||
b = Bounty(
|
||||
id=1,
|
||||
text=None,
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
)
|
||||
assert b.text is None
|
||||
assert b.link is None
|
||||
assert b.due_date_ts is None
|
||||
|
||||
def test_bounty_comparison(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)
|
||||
b2 = Bounty(id=1, text="a", link=None, due_date_ts=None, created_at=0)
|
||||
assert b1 == b2
|
||||
|
||||
|
||||
class TestGroupBounty:
|
||||
def test_create_group_bounty(self):
|
||||
gb = GroupBounty(
|
||||
id=1,
|
||||
text="Group task",
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=123456,
|
||||
)
|
||||
assert gb.id == 1
|
||||
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:
|
||||
def test_create_tracked_bounty(self):
|
||||
tb = TrackedBounty(bounty_id=5, created_at=1735600000)
|
||||
assert tb.bounty_id == 5
|
||||
assert tb.created_at == 1735600000
|
||||
|
||||
def test_tracked_bounty_comparison(self):
|
||||
tb1 = TrackedBounty(bounty_id=1, created_at=0)
|
||||
tb2 = TrackedBounty(bounty_id=1, created_at=0)
|
||||
assert tb1 == tb2
|
||||
|
||||
|
||||
class TestGroupData:
|
||||
def test_create_group_data(self):
|
||||
gd = GroupData(
|
||||
group_id=-1001,
|
||||
bounties=[],
|
||||
next_id=1,
|
||||
)
|
||||
assert gd.group_id == -1001
|
||||
assert gd.bounties == []
|
||||
assert gd.next_id == 1
|
||||
|
||||
def test_group_data_with_bounties(self):
|
||||
gb = GroupBounty(
|
||||
id=1,
|
||||
text="Task",
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=123,
|
||||
)
|
||||
gd = GroupData(group_id=-1001, bounties=[gb], next_id=2)
|
||||
assert len(gd.bounties) == 1
|
||||
assert gd.bounties[0].text == "Task"
|
||||
|
||||
|
||||
class TestTrackingData:
|
||||
def test_create_tracking_data(self):
|
||||
td = TrackingData(user_id=123456, tracked=[])
|
||||
assert td.user_id == 123456
|
||||
assert td.tracked == []
|
||||
|
||||
def test_tracking_data_with_tracked(self):
|
||||
tb = TrackedBounty(bounty_id=5, created_at=0)
|
||||
td = TrackingData(user_id=123, tracked=[tb])
|
||||
assert len(td.tracked) == 1
|
||||
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