Added search endpoints

This commit is contained in:
Cal Corum 2025-10-08 14:21:44 -05:00
parent b20d0cdf88
commit db0635b01d
2 changed files with 160 additions and 1 deletions

View File

@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from fastapi import APIRouter, Depends, HTTPException, Response, Query
from typing import Optional
import logging
import pydantic
@ -84,6 +84,79 @@ async def get_cardsets(
return return_val
@router.get('/search')
async def search_cardsets(
q: str = Query(..., description="Search query for cardset name"),
in_packs: Optional[bool] = None,
ranked_legal: Optional[bool] = None,
event_id: Optional[int] = None,
limit: int = Query(default=25, ge=1, le=100, description="Maximum number of results to return")):
"""
Real-time fuzzy search for cardsets by name.
Returns cardsets matching the query with exact matches prioritized over partial matches.
"""
# Start with all cardsets
all_cardsets = Cardset.select()
# Apply name filter (partial match)
all_cardsets = all_cardsets.where(fn.Lower(Cardset.name).contains(q.lower()))
# Apply optional filters
if in_packs is not None:
all_cardsets = all_cardsets.where(Cardset.in_packs == in_packs)
if ranked_legal is not None:
all_cardsets = all_cardsets.where(Cardset.ranked_legal == ranked_legal)
if event_id is not None:
try:
this_event = Event.get_by_id(event_id)
all_cardsets = all_cardsets.where(Cardset.event == this_event)
except Exception as e:
logging.error(f'Failed to find event {event_id}: {e}')
db.close()
raise HTTPException(status_code=404, detail=f'Event id {event_id} not found')
# Convert to list for sorting
cardsets_list = list(all_cardsets)
# Sort by relevance (exact matches first, then name starts, then partial)
query_lower = q.lower()
exact_matches = []
name_start_matches = []
partial_matches = []
for cardset in cardsets_list:
name_lower = cardset.name.lower()
if name_lower == query_lower:
exact_matches.append(cardset)
else:
# Check if query matches the start of any word in name
name_parts = name_lower.split()
starts_with_match = any(part.startswith(query_lower) for part in name_parts)
if starts_with_match:
name_start_matches.append(cardset)
elif query_lower in name_lower:
partial_matches.append(cardset)
# Combine and limit results (exact, then name starts, then partial)
results = exact_matches + name_start_matches + partial_matches
total_matches = len(results)
limited_results = results[:limit]
# Build response
return_val = {
'count': len(limited_results),
'total_matches': total_matches,
'cardsets': [model_to_dict(x) for x in limited_results]
}
db.close()
return return_val
@router.get('/{cardset_id}')
async def get_one_cardset(cardset_id, csv: Optional[bool] = False):
try:

View File

@ -331,6 +331,92 @@ async def get_random_player(
return return_val
@router.get('/search')
async def search_players(
q: str = Query(..., description="Search query for player name"),
cardset_id: list = Query(default=None),
rarity_id: list = Query(default=None),
limit: int = Query(default=25, ge=1, le=100, description="Maximum number of results to return"),
unique_names: bool = Query(default=False, description="Return only unique player names (highest player_id)"),
short_output: bool = False):
"""
Real-time fuzzy search for players by name.
Returns players matching the query with exact matches prioritized over partial matches.
When unique_names=True, only returns one player per unique name (the one with highest player_id).
"""
# Start with all players
all_players = Player.select()
# Apply name filter (partial match)
all_players = all_players.where(fn.Lower(Player.p_name).contains(q.lower()))
# Apply optional filters
if cardset_id is not None:
all_players = all_players.where(Player.cardset_id << cardset_id)
if rarity_id is not None:
all_players = all_players.where(Player.rarity_id << rarity_id)
# Convert to list for sorting
players_list = list(all_players)
# Sort by relevance (exact matches first, then name starts, then partial)
query_lower = q.lower()
exact_matches = []
name_start_matches = []
partial_matches = []
for player in players_list:
name_lower = player.p_name.lower()
if name_lower == query_lower:
exact_matches.append(player)
else:
# Check if query matches the start of first or last name
name_parts = name_lower.split()
starts_with_match = any(part.startswith(query_lower) for part in name_parts)
if starts_with_match:
name_start_matches.append(player)
elif query_lower in name_lower:
partial_matches.append(player)
# Combine results (exact, then name starts, then partial)
results = exact_matches + name_start_matches + partial_matches
# Deduplicate by name if requested (keeping highest player_id)
if unique_names:
seen_names = {}
for player in results:
name_lower = player.p_name.lower()
if name_lower not in seen_names or player.player_id > seen_names[name_lower].player_id:
seen_names[name_lower] = player
results = list(seen_names.values())
total_matches = len(results)
limited_results = results[:limit]
# Build response
return_val = {
'count': len(limited_results),
'total_matches': total_matches,
'players': []
}
for x in limited_results:
this_record = model_to_dict(x, recurse=not short_output)
# this_dex = Paperdex.select().where(Paperdex.player == x)
# this_record['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
# for y in this_dex:
# this_record['paperdex']['paperdex'].append(model_to_dict(y, recurse=False))
return_val['players'].append(this_record)
db.close()
return return_val
@router.get('/{player_id}')
async def get_one_player(player_id, csv: Optional[bool] = False):
try: