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
|
from typing import Optional
|
||||||
import logging
|
import logging
|
||||||
import pydantic
|
import pydantic
|
||||||
@ -84,6 +84,79 @@ async def get_cardsets(
|
|||||||
return return_val
|
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}')
|
@router.get('/{cardset_id}')
|
||||||
async def get_one_cardset(cardset_id, csv: Optional[bool] = False):
|
async def get_one_cardset(cardset_id, csv: Optional[bool] = False):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -331,6 +331,92 @@ async def get_random_player(
|
|||||||
return return_val
|
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}')
|
@router.get('/{player_id}')
|
||||||
async def get_one_player(player_id, csv: Optional[bool] = False):
|
async def get_one_player(player_id, csv: Optional[bool] = False):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user