Merge pull request 'feat: Category Feature - Models & Storage (#85)' (#90) from feature/category-models-storage into main

This commit was merged in pull request #90.
This commit is contained in:
2026-04-09 12:35:50 +02:00
2 changed files with 45 additions and 2 deletions

View File

@@ -11,7 +11,7 @@ import os
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from core.models import Bounty, RoomData, TrackingData, TrackedBounty from core.models import Bounty, Category, RoomData, TrackingData, TrackedBounty
class JsonFileRoomStorage: class JsonFileRoomStorage:
@@ -57,16 +57,28 @@ class JsonFileRoomStorage:
created_by_user_id=b["created_by_user_id"], created_by_user_id=b["created_by_user_id"],
deleted_at=b.get("deleted_at"), deleted_at=b.get("deleted_at"),
created_by_username=b.get("created_by_username"), created_by_username=b.get("created_by_username"),
category_ids=b.get("category_ids", []),
) )
for b in data.get("bounties", []) for b in data.get("bounties", [])
] ]
categories = [
Category(
id=c["id"],
name=c["name"],
created_at=c["created_at"],
deleted_at=c.get("deleted_at"),
)
for c in data.get("categories", [])
]
return RoomData( return RoomData(
room_id=data["room_id"], room_id=data["room_id"],
bounties=bounties, bounties=bounties,
next_id=data["next_id"], next_id=data["next_id"],
timezone=data.get("timezone"), timezone=data.get("timezone"),
admin_usernames=data.get("admin_usernames", []), admin_usernames=data.get("admin_usernames", []),
categories=categories,
) )
def save(self, room_data: RoomData) -> None: def save(self, room_data: RoomData) -> None:
@@ -76,6 +88,15 @@ class JsonFileRoomStorage:
"next_id": room_data.next_id, "next_id": room_data.next_id,
"timezone": room_data.timezone, "timezone": room_data.timezone,
"admin_usernames": room_data.admin_usernames or [], "admin_usernames": room_data.admin_usernames or [],
"categories": [
{
"id": c.id,
"name": c.name,
"created_at": c.created_at,
"deleted_at": c.deleted_at,
}
for c in room_data.categories
],
"bounties": [ "bounties": [
{ {
"id": b.id, "id": b.id,
@@ -86,6 +107,7 @@ class JsonFileRoomStorage:
"created_by_user_id": b.created_by_user_id, "created_by_user_id": b.created_by_user_id,
"deleted_at": b.deleted_at, "deleted_at": b.deleted_at,
"created_by_username": b.created_by_username, "created_by_username": b.created_by_username,
"category_ids": b.category_ids,
} }
for b in room_data.bounties for b in room_data.bounties
], ],

View File

@@ -1,6 +1,20 @@
"""Domain dataclasses for JIGAIDO bounty tracker.""" """Domain dataclasses for JIGAIDO bounty tracker."""
from dataclasses import dataclass 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 @dataclass
@@ -12,6 +26,8 @@ class Bounty:
The deleted_at field indicates soft-delete: None means not deleted, The deleted_at field indicates soft-delete: None means not deleted,
a value means deleted at that Unix timestamp. a value means deleted at that Unix timestamp.
The category_ids field lists category slugs associated with this bounty.
""" """
id: int id: int
@@ -22,6 +38,7 @@ class Bounty:
created_by_user_id: int created_by_user_id: int
deleted_at: int | None = None deleted_at: int | None = None
created_by_username: str | None = None created_by_username: str | None = None
category_ids: list[str] = field(default_factory=list)
@dataclass @dataclass
@@ -45,6 +62,7 @@ class RoomData:
The timezone field stores the room's timezone (e.g., "Asia/Jakarta"), default UTC+0. 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 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 room_id: int
@@ -52,10 +70,13 @@ class RoomData:
next_id: int next_id: int
timezone: str | None = None timezone: str | None = None
admin_usernames: list[str] | None = None admin_usernames: list[str] | None = None
categories: list[Category] = field(default_factory=list)
def __post_init__(self): def __post_init__(self):
if self.admin_usernames is None: if self.admin_usernames is None:
self.admin_usernames = [] self.admin_usernames = []
if self.categories is None:
self.categories = []
@dataclass @dataclass