Compare commits

...

2 Commits

Author SHA1 Message Date
Ruslan Bakiev
e15976382e Add hubCountries query and country filter for nodes
All checks were successful
Build Docker Image / build (push) Successful in 1m45s
2026-01-08 10:42:34 +07:00
Ruslan Bakiev
5d5e0fa4b5 Add nearestNodes query for coordinate-based search
All checks were successful
Build Docker Image / build (push) Successful in 1m39s
2026-01-08 00:36:16 +07:00

View File

@@ -96,11 +96,26 @@ class Query(graphene.ObjectType):
limit=graphene.Int(), limit=graphene.Int(),
offset=graphene.Int(), offset=graphene.Int(),
transport_type=graphene.String(), transport_type=graphene.String(),
country=graphene.String(description="Filter by country name"),
search=graphene.String(description="Search by node name (case-insensitive)"), search=graphene.String(description="Search by node name (case-insensitive)"),
) )
nodes_count = graphene.Int( nodes_count = graphene.Int(
transport_type=graphene.String(), transport_type=graphene.String(),
description="Get total count of nodes (with optional transport filter)", country=graphene.String(description="Filter by country name"),
description="Get total count of nodes (with optional transport/country filter)",
)
hub_countries = graphene.List(
graphene.String,
description="List of countries that have logistics hubs",
)
nearest_nodes = graphene.List(
NodeType,
lat=graphene.Float(required=True, description="Latitude"),
lon=graphene.Float(required=True, description="Longitude"),
limit=graphene.Int(default_value=5, description="Max results"),
description="Find nearest logistics nodes to given coordinates",
) )
node_connections = graphene.Field( node_connections = graphene.Field(
@@ -264,7 +279,7 @@ class Query(graphene.ObjectType):
edges=[EdgeType(**e) for e in edges], edges=[EdgeType(**e) for e in edges],
) )
def resolve_nodes(self, info, limit=None, offset=None, transport_type=None, search=None): def resolve_nodes(self, info, limit=None, offset=None, transport_type=None, country=None, search=None):
"""Get all logistics nodes (without edges for list view).""" """Get all logistics nodes (without edges for list view)."""
db = get_db() db = get_db()
@@ -274,6 +289,7 @@ class Query(graphene.ObjectType):
FILTER node.node_type == 'logistics' OR node.node_type == null FILTER node.node_type == 'logistics' OR node.node_type == 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 @search == null OR CONTAINS(LOWER(node.name), LOWER(@search)) OR CONTAINS(LOWER(node.country), LOWER(@search)) FILTER @search == null OR CONTAINS(LOWER(node.name), LOWER(@search)) OR CONTAINS(LOWER(node.country), LOWER(@search))
SORT node.name ASC SORT node.name ASC
LIMIT @offset, @limit LIMIT @offset, @limit
@@ -283,6 +299,7 @@ class Query(graphene.ObjectType):
aql, aql,
bind_vars={ bind_vars={
'transport_type': transport_type, 'transport_type': transport_type,
'country': country,
'search': search, 'search': search,
'offset': 0 if offset is None else offset, 'offset': 0 if offset is None else offset,
'limit': 1000000 if limit is None else limit, 'limit': 1000000 if limit is None else limit,
@@ -306,19 +323,69 @@ class Query(graphene.ObjectType):
logger.info("Returning %d nodes", len(nodes)) logger.info("Returning %d nodes", len(nodes))
return nodes return nodes
def resolve_nodes_count(self, info, transport_type=None): def resolve_nodes_count(self, info, transport_type=None, country=None):
db = get_db() db = get_db()
aql = """ aql = """
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
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
COLLECT WITH COUNT INTO length COLLECT WITH COUNT INTO length
RETURN length RETURN length
""" """
cursor = db.aql.execute(aql, bind_vars={'transport_type': transport_type}) cursor = db.aql.execute(aql, bind_vars={'transport_type': transport_type, 'country': country})
return next(cursor, 0) return next(cursor, 0)
def resolve_hub_countries(self, info):
"""Get unique country names from logistics hubs."""
db = get_db()
aql = """
FOR node IN nodes
FILTER node.node_type == 'logistics' OR node.node_type == null
FILTER node.country != null
COLLECT country = node.country
SORT country ASC
RETURN country
"""
cursor = db.aql.execute(aql)
return list(cursor)
def resolve_nearest_nodes(self, info, lat, lon, limit=5):
"""Find nearest logistics nodes to given coordinates."""
db = get_db()
# Get all logistics nodes and calculate distance
aql = """
FOR node IN nodes
FILTER node.node_type == 'logistics' OR node.node_type == null
FILTER node.latitude != null AND node.longitude != null
LET dist = DISTANCE(node.latitude, node.longitude, @lat, @lon) / 1000
SORT dist ASC
LIMIT @limit
RETURN MERGE(node, {distance_km: dist})
"""
cursor = db.aql.execute(
aql,
bind_vars={'lat': lat, 'lon': lon, 'limit': limit},
)
nodes = []
for node in cursor:
nodes.append(NodeType(
uuid=node['_key'],
name=node.get('name'),
latitude=node.get('latitude'),
longitude=node.get('longitude'),
country=node.get('country'),
country_code=node.get('country_code'),
synced_at=node.get('synced_at'),
transport_types=node.get('transport_types') or [],
edges=[],
))
return nodes
def resolve_node_connections(self, info, uuid, limit_auto=12, limit_rail=12): def resolve_node_connections(self, info, uuid, limit_auto=12, limit_rail=12):
"""Get auto edges from hub and rail edges from nearest rail node.""" """Get auto edges from hub and rail edges from nearest rail node."""
db = get_db() db = get_db()