Added search endpoints
This commit is contained in:
parent
b20d0cdf88
commit
db0635b01d
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user