Models & Storage layer for category feature (Issue #85): - Add Category dataclass to core/models.py - id (slug): lowercase alphabetic only - name: display name - created_at: Unix timestamp - deleted_at: soft delete timestamp (None if active) - Add category_ids field to Bounty dataclass - list[str] for multiple categories per bounty - Default empty list for backward compatibility - Add categories field to RoomData dataclass - list[Category] for room-level categories - Default empty list - Update JsonFileRoomStorage to serialize/deserialize: - Category fields (id, name, created_at, deleted_at) - Bounty.category_ids - RoomData.categories Backward compatible: existing data without categories works fine.
97 lines
2.8 KiB
Python
97 lines
2.8 KiB
Python
"""Domain dataclasses for JIGAIDO bounty tracker."""
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass
|
|
class Category:
|
|
"""A category for organizing bounties in a room.
|
|
|
|
Categories are per-room and support soft delete.
|
|
The id (slug) must be lowercase alphabetic only (e.g., "bug", "feature").
|
|
"""
|
|
|
|
id: str # slug: lowercase alphabetic only, e.g., "bug", "feature"
|
|
name: str # display name: e.g., "Bug", "Feature"
|
|
created_at: int
|
|
deleted_at: int | None = None # soft delete
|
|
|
|
|
|
@dataclass
|
|
class Bounty:
|
|
"""A bounty created by a user.
|
|
|
|
The created_by_user_id field always refers to the user who created the bounty.
|
|
It does NOT indicate whether the bounty is a group or personal bounty.
|
|
|
|
The deleted_at field indicates soft-delete: None means not deleted,
|
|
a value means deleted at that Unix timestamp.
|
|
|
|
The category_ids field lists category slugs associated with this bounty.
|
|
"""
|
|
|
|
id: int
|
|
text: str | None
|
|
link: str | None
|
|
due_date_ts: int | None
|
|
created_at: int
|
|
created_by_user_id: int
|
|
deleted_at: int | None = None
|
|
created_by_username: str | None = None
|
|
category_ids: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class TrackedBounty:
|
|
"""A bounty that a user is tracking.
|
|
|
|
Lightweight relation/pointer - the actual tracking context (including room)
|
|
lives in TrackingData, not here.
|
|
"""
|
|
|
|
bounty_id: int
|
|
created_at: int
|
|
|
|
|
|
@dataclass
|
|
class RoomData:
|
|
"""All data for a room (group or DM).
|
|
|
|
The room_id can be negative for Telegram groups or positive for DMs.
|
|
The next_id field is used to generate unique bounty IDs within this room.
|
|
|
|
The timezone field stores the room's timezone (e.g., "Asia/Jakarta"), default UTC+0.
|
|
The admin_usernames field lists usernames who have admin privileges in this room.
|
|
The categories field contains all categories for organizing bounties in this room.
|
|
"""
|
|
|
|
room_id: int
|
|
bounties: list[Bounty]
|
|
next_id: int
|
|
timezone: str | None = None
|
|
admin_usernames: list[str] | None = None
|
|
categories: list[Category] = field(default_factory=list)
|
|
|
|
def __post_init__(self):
|
|
if self.admin_usernames is None:
|
|
self.admin_usernames = []
|
|
if self.categories is None:
|
|
self.categories = []
|
|
|
|
|
|
@dataclass
|
|
class TrackingData:
|
|
"""User tracking state within a room (group or DM).
|
|
|
|
TrackingData vs TrackedBounty:
|
|
- Use TrackingData to store ALL tracked bounties for a user in a specific room.
|
|
It contains the room_id, user_id, and a list of TrackedBounty entries.
|
|
- Use TrackedBounty to represent a single tracked bounty entry within that list.
|
|
|
|
TrackingData is the container, TrackedBounty is the item.
|
|
"""
|
|
|
|
room_id: int
|
|
user_id: int
|
|
tracked: list[TrackedBounty]
|