Add listing decisions (like/dislike) backend with detail endpoint
- ListingDecision model with unique constraint on (user_id, listing_id, listing_type)
- Alembic migration for listingdecision table
- DecisionRepository with dialect-aware upsert (MySQL/SQLite)
- DecisionService with input validation
- Decision API routes: PUT/GET/DELETE on /api/decisions
- GET /api/listing/{id}/detail endpoint extracting full property info from additional_info
- Add listing ID to GeoJSON feature properties
- Decision filtering on GeoJSON stream endpoint (decision_filter param)
This commit is contained in:
parent
a2e7d59af2
commit
9e1beb7495
7 changed files with 447 additions and 138 deletions
|
|
@ -1,4 +1,8 @@
|
|||
"""Unified decision service -- shared between CLI and HTTP API."""
|
||||
"""Unified decision service - shared between CLI and HTTP API.
|
||||
|
||||
This module provides the core business logic for listing decision operations
|
||||
(like/dislike). Both the CLI and HTTP API should use these functions.
|
||||
"""
|
||||
from models.decision import ListingDecision
|
||||
from repositories.decision_repository import DecisionRepository
|
||||
|
||||
|
|
@ -13,47 +17,53 @@ def set_decision(
|
|||
listing_type: str,
|
||||
decision: str,
|
||||
) -> ListingDecision:
|
||||
"""Set or update a like/dislike decision for a listing."""
|
||||
if decision not in VALID_DECISIONS:
|
||||
raise ValueError(
|
||||
f"Invalid decision '{decision}'. Must be one of: {VALID_DECISIONS}"
|
||||
f"Invalid decision: {decision}. Must be one of {VALID_DECISIONS}"
|
||||
)
|
||||
if listing_type not in VALID_LISTING_TYPES:
|
||||
raise ValueError(
|
||||
f"Invalid listing_type '{listing_type}'. Must be one of: {VALID_LISTING_TYPES}"
|
||||
f"Invalid listing_type: {listing_type}. Must be one of {VALID_LISTING_TYPES}"
|
||||
)
|
||||
return repository.upsert_decision(
|
||||
user_id=user_id,
|
||||
listing_id=listing_id,
|
||||
listing_type=listing_type,
|
||||
decision=decision,
|
||||
)
|
||||
return repository.upsert_decision(user_id, listing_id, listing_type, decision)
|
||||
|
||||
|
||||
def get_decisions(
|
||||
def get_user_decisions(
|
||||
repository: DecisionRepository,
|
||||
user_id: int,
|
||||
) -> list[ListingDecision]:
|
||||
"""Get all decisions for a user."""
|
||||
return repository.get_decisions_for_user(user_id)
|
||||
|
||||
|
||||
def clear_decision(
|
||||
def remove_decision(
|
||||
repository: DecisionRepository,
|
||||
user_id: int,
|
||||
listing_id: int,
|
||||
listing_type: str,
|
||||
) -> bool:
|
||||
return repository.delete_decision(
|
||||
user_id=user_id,
|
||||
listing_id=listing_id,
|
||||
listing_type=listing_type,
|
||||
)
|
||||
"""Remove a decision (un-like/un-dislike). Returns False if not found."""
|
||||
if listing_type not in VALID_LISTING_TYPES:
|
||||
raise ValueError(
|
||||
f"Invalid listing_type: {listing_type}. Must be one of {VALID_LISTING_TYPES}"
|
||||
)
|
||||
return repository.delete_decision(user_id, listing_id, listing_type)
|
||||
|
||||
|
||||
def get_disliked_listing_ids(
|
||||
def get_disliked_ids(
|
||||
repository: DecisionRepository,
|
||||
user_id: int,
|
||||
listing_type: str,
|
||||
) -> set[int]:
|
||||
return repository.get_disliked_listing_ids(
|
||||
user_id=user_id, listing_type=listing_type
|
||||
)
|
||||
"""Get all disliked listing IDs for a user and listing type."""
|
||||
return repository.get_disliked_listing_ids(user_id, listing_type)
|
||||
|
||||
|
||||
def get_liked_ids(
|
||||
repository: DecisionRepository,
|
||||
user_id: int,
|
||||
listing_type: str,
|
||||
) -> set[int]:
|
||||
"""Get all liked listing IDs for a user and listing type."""
|
||||
return repository.get_liked_listing_ids(user_id, listing_type)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue