snipe/app/platforms/__init__.py
pyr0ball f48f8ef80f feat: multi-platform scaffolding — phase 1 (eBay-only, wire complete)
Backend:
- app/platforms/__init__.py: add SUPPORTED_PLATFORMS frozenset (single
  source of truth for platform validation); add must_include_mode and
  adapter fields to SearchFilters dataclass
- api/main.py: add platform: str = Query("ebay") to both /api/search
  and /api/search/async; validate against SUPPORTED_PLATFORMS (422 on
  unknown platform); thread platform into structured log lines; document
  Phase 2 registry extension point in _make_adapter

Frontend:
- SearchView.vue: platform tab strip (eBay active, Mercari + Poshmark
  disabled with "soon" badge) above search bar; eBay-specific controls
  (category select, data source, pages, keywords) hidden when platform
  !== 'ebay'; platform passed to SearchProgress
- search.ts: platform?: string added to SearchFilters; included in
  async search params when non-eBay
- SearchProgress.vue: platform prop + PLATFORM_LABELS map; status line
  reads "Searching eBay for…" / "Searching Mercari for…" dynamically
2026-05-02 20:09:36 -07:00

39 lines
1.5 KiB
Python

"""PlatformAdapter abstract base and shared types."""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional
from app.db.models import Listing, Seller
# Single source of truth for platform validation.
# Phase 2 will extend this set as new adapters are implemented.
SUPPORTED_PLATFORMS: frozenset[str] = frozenset({"ebay"})
@dataclass
class SearchFilters:
max_price: Optional[float] = None
min_price: Optional[float] = None
condition: Optional[list[str]] = field(default_factory=list)
location_radius_km: Optional[int] = None
pages: int = 1 # number of result pages to fetch (48 listings/page)
must_include: list[str] = field(default_factory=list) # client-side title filter
must_exclude: list[str] = field(default_factory=list) # forwarded to eBay -term AND client-side
category_id: Optional[str] = None # eBay category ID (e.g. "27386" = GPUs)
must_include_mode: str = "all" # "all" | "any" | "groups"
adapter: str = "auto" # "auto" | "api" | "scraper"
class PlatformAdapter(ABC):
@abstractmethod
def search(self, query: str, filters: SearchFilters) -> list[Listing]: ...
@abstractmethod
def get_seller(self, seller_platform_id: str) -> Optional[Seller]: ...
@abstractmethod
def get_completed_sales(self, query: str) -> list[Listing]:
"""Fetch recently completed/sold listings for price comp data."""
...