feat: add list_bounties and list_all_bounties methods to storage adapter

Add filtering methods to JsonFileRoomStorage for Phase 2 soft delete support:
- list_bounties(room_id): returns only non-deleted bounties for normal queries
- list_all_bounties(room_id, include_deleted=True): returns all bounties for /recover

Update RoomStorage protocol to include the new methods.
Update mock classes in tests to pass isinstance checks.

Fixes #42
This commit is contained in:
shokollm
2026-04-04 05:12:19 +00:00
parent d413f6ce13
commit ed0d31bc04
3 changed files with 76 additions and 0 deletions

View File

@@ -140,6 +140,35 @@ class JsonFileRoomStorage:
return None return None
def list_bounties(self, room_id: int) -> list[Bounty]:
"""List all non-deleted bounties in a room.
This is the default method for normal queries - soft-deleted bounties
are excluded from results.
"""
room_data = self.load(room_id)
if room_data is None:
return []
return [b for b in room_data.bounties if b.deleted_at is None]
def list_all_bounties(
self, room_id: int, include_deleted: bool = True
) -> list[Bounty]:
"""List all bounties including or excluding soft-deleted.
Args:
room_id: The room ID
include_deleted: If True, return all bounties including soft-deleted.
If False, return only non-deleted bounties.
Defaults to True for /recover functionality.
"""
room_data = self.load(room_id)
if room_data is None:
return []
if include_deleted:
return room_data.bounties
return [b for b in room_data.bounties if b.deleted_at is None]
class JsonFileTrackingStorage: class JsonFileTrackingStorage:
"""TrackingStorage implementation using JSON files. """TrackingStorage implementation using JSON files.

View File

@@ -40,6 +40,25 @@ class RoomStorage(Protocol):
"""Get a specific bounty from a room by ID.""" """Get a specific bounty from a room by ID."""
... ...
def list_bounties(self, room_id: int) -> list[Bounty]:
"""List all non-deleted bounties in a room.
Soft-deleted bounties (where deleted_at is not None) are excluded.
"""
...
def list_all_bounties(
self, room_id: int, include_deleted: bool = True
) -> list[Bounty]:
"""List all bounties including or excluding soft-deleted.
Args:
room_id: The room ID
include_deleted: If True, return all bounties including soft-deleted.
If False, return only non-deleted bounties.
"""
...
@runtime_checkable @runtime_checkable
class TrackingStorage(Protocol): class TrackingStorage(Protocol):

View File

@@ -48,6 +48,20 @@ class SimpleRoomStorage:
return b return b
return None return None
def list_bounties(self, room_id: int) -> list[Bounty]:
if room_id not in self._rooms:
return []
return [b for b in self._rooms[room_id].bounties if b.deleted_at is None]
def list_all_bounties(
self, room_id: int, include_deleted: bool = True
) -> list[Bounty]:
if room_id not in self._rooms:
return []
if include_deleted:
return self._rooms[room_id].bounties
return [b for b in self._rooms[room_id].bounties if b.deleted_at is None]
class SimpleTrackingStorage: class SimpleTrackingStorage:
"""Minimal mock without ensure_tracking - tests if track_bounty works without it. """Minimal mock without ensure_tracking - tests if track_bounty works without it.
@@ -119,6 +133,20 @@ class MockRoomStorage:
return b return b
return None return None
def list_bounties(self, room_id: int) -> list[Bounty]:
if room_id not in self._rooms:
return []
return [b for b in self._rooms[room_id].bounties if b.deleted_at is None]
def list_all_bounties(
self, room_id: int, include_deleted: bool = True
) -> list[Bounty]:
if room_id not in self._rooms:
return []
if include_deleted:
return self._rooms[room_id].bounties
return [b for b in self._rooms[room_id].bounties if b.deleted_at is None]
class MockTrackingStorage: class MockTrackingStorage:
"""Mock implementation of TrackingStorage for testing.""" """Mock implementation of TrackingStorage for testing."""