feat(schema): add bounds filtering to list endpoints
All checks were successful
Build Docker Image / build (push) Successful in 1m16s

Add west, south, east, north params to:
- hubs_list
- suppliers_list
- products_list

This enables filtering by map viewport bounds for the catalog.
This commit is contained in:
Ruslan Bakiev
2026-01-26 21:35:20 +07:00
parent ca01a91019
commit 9db56c5edc

View File

@@ -365,6 +365,10 @@ class Query(graphene.ObjectType):
offset=graphene.Int(default_value=0, description="Offset for pagination"), offset=graphene.Int(default_value=0, description="Offset for pagination"),
country=graphene.String(description="Filter by country name"), country=graphene.String(description="Filter by country name"),
transport_type=graphene.String(description="Filter by transport type"), transport_type=graphene.String(description="Filter by transport type"),
west=graphene.Float(description="Bounding box west longitude"),
south=graphene.Float(description="Bounding box south latitude"),
east=graphene.Float(description="Bounding box east longitude"),
north=graphene.Float(description="Bounding box north latitude"),
description="Get paginated list of logistics hubs", description="Get paginated list of logistics hubs",
) )
@@ -373,6 +377,10 @@ class Query(graphene.ObjectType):
limit=graphene.Int(default_value=50, description="Max results"), limit=graphene.Int(default_value=50, description="Max results"),
offset=graphene.Int(default_value=0, description="Offset for pagination"), offset=graphene.Int(default_value=0, description="Offset for pagination"),
country=graphene.String(description="Filter by country name"), country=graphene.String(description="Filter by country name"),
west=graphene.Float(description="Bounding box west longitude"),
south=graphene.Float(description="Bounding box south latitude"),
east=graphene.Float(description="Bounding box east longitude"),
north=graphene.Float(description="Bounding box north latitude"),
description="Get paginated list of suppliers from graph", description="Get paginated list of suppliers from graph",
) )
@@ -380,6 +388,10 @@ class Query(graphene.ObjectType):
ProductType, ProductType,
limit=graphene.Int(default_value=50, description="Max results"), limit=graphene.Int(default_value=50, description="Max results"),
offset=graphene.Int(default_value=0, description="Offset for pagination"), offset=graphene.Int(default_value=0, description="Offset for pagination"),
west=graphene.Float(description="Bounding box west longitude"),
south=graphene.Float(description="Bounding box south latitude"),
east=graphene.Float(description="Bounding box east longitude"),
north=graphene.Float(description="Bounding box north latitude"),
description="Get paginated list of products from graph", description="Get paginated list of products from graph",
) )
@@ -1683,30 +1695,52 @@ class Query(graphene.ObjectType):
logger.error("Error finding route to coordinates: %s", e) logger.error("Error finding route to coordinates: %s", e)
return None return None
def resolve_hubs_list(self, info, limit=50, offset=0, country=None, transport_type=None): def resolve_hubs_list(self, info, limit=50, offset=0, country=None, transport_type=None,
west=None, south=None, east=None, north=None):
"""Get paginated list of logistics hubs.""" """Get paginated list of logistics hubs."""
db = get_db() db = get_db()
aql = """ # Build bounds filter if all bounds are provided
bounds_filter = ""
if west is not None and south is not None and east is not None and north is not None:
bounds_filter = """
FILTER node.latitude != null AND node.longitude != null
FILTER node.latitude >= @south AND node.latitude <= @north
FILTER node.longitude >= @west AND node.longitude <= @east
"""
aql = f"""
FOR node IN nodes FOR node IN nodes
FILTER node.node_type == 'logistics' OR node.node_type == null FILTER node.node_type == 'logistics' OR node.node_type == null
FILTER node.product_uuid == null FILTER node.product_uuid == null
LET types = node.transport_types != null ? node.transport_types : [] LET types = node.transport_types != null ? node.transport_types : []
FILTER @transport_type == null OR @transport_type IN types FILTER @transport_type == null OR @transport_type IN types
FILTER @country == null OR node.country == @country FILTER @country == null OR node.country == @country
{bounds_filter}
SORT node.name ASC SORT node.name ASC
LIMIT @offset, @limit LIMIT @offset, @limit
RETURN node RETURN node
""" """
try: bind_vars = {
cursor = db.aql.execute(aql, bind_vars={
'transport_type': transport_type, 'transport_type': transport_type,
'country': country, 'country': country,
'offset': offset, 'offset': offset,
'limit': limit, 'limit': limit,
}
# Only add bounds to bind_vars if they are used
if west is not None and south is not None and east is not None and north is not None:
bind_vars.update({
'west': west,
'south': south,
'east': east,
'north': north,
}) })
try:
cursor = db.aql.execute(aql, bind_vars=bind_vars)
hubs = [] hubs = []
for node in cursor: for node in cursor:
hubs.append(NodeType( hubs.append(NodeType(
@@ -1726,26 +1760,48 @@ class Query(graphene.ObjectType):
logger.error("Error getting hubs list: %s", e) logger.error("Error getting hubs list: %s", e)
return [] return []
def resolve_suppliers_list(self, info, limit=50, offset=0, country=None): def resolve_suppliers_list(self, info, limit=50, offset=0, country=None,
west=None, south=None, east=None, north=None):
"""Get paginated list of suppliers from graph.""" """Get paginated list of suppliers from graph."""
db = get_db() db = get_db()
aql = """ # Build bounds filter if all bounds are provided
bounds_filter = ""
if west is not None and south is not None and east is not None and north is not None:
bounds_filter = """
FILTER node.latitude != null AND node.longitude != null
FILTER node.latitude >= @south AND node.latitude <= @north
FILTER node.longitude >= @west AND node.longitude <= @east
"""
aql = f"""
FOR node IN nodes FOR node IN nodes
FILTER node.node_type == 'supplier' FILTER node.node_type == 'supplier'
FILTER @country == null OR node.country == @country FILTER @country == null OR node.country == @country
{bounds_filter}
SORT node.name ASC SORT node.name ASC
LIMIT @offset, @limit LIMIT @offset, @limit
RETURN node RETURN node
""" """
try: bind_vars = {
cursor = db.aql.execute(aql, bind_vars={
'country': country, 'country': country,
'offset': offset, 'offset': offset,
'limit': limit, 'limit': limit,
}
# Only add bounds to bind_vars if they are used
if west is not None and south is not None and east is not None and north is not None:
bind_vars.update({
'west': west,
'south': south,
'east': east,
'north': north,
}) })
try:
cursor = db.aql.execute(aql, bind_vars=bind_vars)
suppliers = [] suppliers = []
for node in cursor: for node in cursor:
suppliers.append(SupplierType( suppliers.append(SupplierType(
@@ -1760,30 +1816,52 @@ class Query(graphene.ObjectType):
logger.error("Error getting suppliers list: %s", e) logger.error("Error getting suppliers list: %s", e)
return [] return []
def resolve_products_list(self, info, limit=50, offset=0): def resolve_products_list(self, info, limit=50, offset=0,
west=None, south=None, east=None, north=None):
"""Get paginated list of products from graph.""" """Get paginated list of products from graph."""
db = get_db() db = get_db()
aql = """ # Build bounds filter if all bounds are provided
bounds_filter = ""
if west is not None and south is not None and east is not None and north is not None:
bounds_filter = """
FILTER node.latitude != null AND node.longitude != null
FILTER node.latitude >= @south AND node.latitude <= @north
FILTER node.longitude >= @west AND node.longitude <= @east
"""
aql = f"""
FOR node IN nodes FOR node IN nodes
FILTER node.node_type == 'offer' FILTER node.node_type == 'offer'
FILTER node.product_uuid != null FILTER node.product_uuid != null
{bounds_filter}
COLLECT product_uuid = node.product_uuid INTO offers COLLECT product_uuid = node.product_uuid INTO offers
LET first_offer = FIRST(offers).node LET first_offer = FIRST(offers).node
SORT first_offer.product_name ASC SORT first_offer.product_name ASC
LIMIT @offset, @limit LIMIT @offset, @limit
RETURN { RETURN {{
uuid: product_uuid, uuid: product_uuid,
name: first_offer.product_name name: first_offer.product_name
} }}
""" """
try: bind_vars = {
cursor = db.aql.execute(aql, bind_vars={
'offset': offset, 'offset': offset,
'limit': limit, 'limit': limit,
}
# Only add bounds to bind_vars if they are used
if west is not None and south is not None and east is not None and north is not None:
bind_vars.update({
'west': west,
'south': south,
'east': east,
'north': north,
}) })
try:
cursor = db.aql.execute(aql, bind_vars=bind_vars)
products = [ProductType(uuid=p['uuid'], name=p.get('name')) for p in cursor] products = [ProductType(uuid=p['uuid'], name=p.get('name')) for p in cursor]
logger.info("Returning %d products (offset=%d, limit=%d)", len(products), offset, limit) logger.info("Returning %d products (offset=%d, limit=%d)", len(products), offset, limit)
return products return products