diff --git a/cli/main.py b/cli/main.py index 06faaba..8a50f9e 100644 --- a/cli/main.py +++ b/cli/main.py @@ -127,6 +127,40 @@ def cmd_delete(args): sys.exit(1) +def cmd_recover(args): + """List or recover soft-deleted bounties.""" + bounty_service, _ = create_services() + room_id = args.group_id or args.user_id + user_id = args.user_id or 0 + + if not args.bounty_ids: + deleted = bounty_service.list_deleted_bounties(room_id) + if not deleted: + print("No recoverable bounties") + return + print("Recoverable bounties:") + for b in deleted: + from datetime import datetime + + deleted_str = datetime.fromtimestamp(b.deleted_at).strftime("%d %b %Y") + print(f" [#{b.id}] {b.text or '(no text)'} | Deleted {deleted_str}") + return + + if not bounty_service.is_admin(room_id, user_id): + print("Error: Only admins can recover bounties.", file=sys.stderr) + sys.exit(1) + + for bounty_id in args.bounty_ids: + try: + success, msg = bounty_service.recover_bounty( + room_id=room_id, bounty_id=bounty_id, user_id=user_id + ) + print(msg) + except PermissionError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + def cmd_track(args): """Track a bounty.""" _, tracking_service = create_services() @@ -196,6 +230,9 @@ def main(): parser_untrack = subparsers.add_parser("untrack", help="Untrack a bounty") parser_untrack.add_argument("bounty_id", type=int, help="Bounty ID to untrack") + parser_recover = subparsers.add_parser("recover", help="List or recover soft-deleted bounties") + parser_recover.add_argument("bounty_ids", nargs="*", type=int, help="Bounty ID(s) to recover (optional)") + for sp in [ parser_add, parser_list, @@ -204,6 +241,7 @@ def main(): parser_delete, parser_track, parser_untrack, + parser_recover, ]: sp.add_argument( "--group-id", type=int, help="Group context (use group room ID)" @@ -222,7 +260,7 @@ def main(): print("Error: either --group-id or --user-id is required", file=sys.stderr) sys.exit(1) - if args.command in ("add", "list", "my", "update", "delete"): + if args.command in ("add", "list", "my", "update", "delete", "recover"): if not (args.group_id or args.user_id): print("Error: --group-id or --user-id required", file=sys.stderr) sys.exit(1) @@ -238,6 +276,7 @@ def main(): "delete": cmd_delete, "track": cmd_track, "untrack": cmd_untrack, + "recover": cmd_recover, } if args.command in command_map: diff --git a/core/services.py b/core/services.py index 2f727b4..ee3ed63 100644 --- a/core/services.py +++ b/core/services.py @@ -210,6 +210,33 @@ class BountyService: self._storage.update_bounty(room_id, bounty) return True + def recover_bounty( + self, room_id: int, bounty_id: int, user_id: int + ) -> tuple[bool, str]: + """Recover a soft-deleted bounty. Only admins can recover. + + Returns (success, message) tuple. + """ + all_bounties = self._storage.list_all_bounties(room_id, include_deleted=True) + bounty = None + for b in all_bounties: + if b.id == bounty_id: + bounty = b + break + + if not bounty: + return False, f"Bounty #{bounty_id} not found." + + if bounty.deleted_at is None: + return False, f"Bounty #{bounty_id} is not deleted." + + if not self.is_admin(room_id, user_id): + raise PermissionError("Only admins can recover bounties.") + + bounty.deleted_at = None + self._storage.update_bounty(room_id, bounty) + return True, f"Recovered bounty #{bounty_id}." + class TrackingService: """Service for tracking bounty operations."""