refactor: convert GraphQL schema from snake_case to camelCase to match frontend expectations
Some checks failed
Build Docker Image / build (push) Failing after 2m12s

This commit is contained in:
Ruslan Bakiev
2026-03-10 10:15:54 +07:00
parent 56a7734e8e
commit 9384566610
2 changed files with 206 additions and 186 deletions

View File

@@ -14,22 +14,22 @@ export function distanceKm(lat1: number, lon1: number, lat2: number, lon2: numbe
export type ArangoDoc = Record<string, any> export type ArangoDoc = Record<string, any>
export interface RouteStage { export interface RouteStage {
from_uuid: string | null fromUuid: string | null
from_name: string | null fromName: string | null
from_lat: number | null fromLat: number | null
from_lon: number | null fromLon: number | null
to_uuid: string | null toUuid: string | null
to_name: string | null toName: string | null
to_lat: number | null toLat: number | null
to_lon: number | null toLon: number | null
distance_km: number distanceKm: number
travel_time_seconds: number travelTimeSeconds: number
transport_type: string | null transportType: string | null
} }
export interface RoutePath { export interface RoutePath {
total_distance_km: number totalDistanceKm: number
total_time_seconds: number totalTimeSeconds: number
stages: RouteStage[] stages: RouteStage[]
} }
@@ -37,17 +37,17 @@ function buildStage(fromDoc: ArangoDoc | undefined, toDoc: ArangoDoc | undefined
const distance = edges.reduce((s, e) => s + (e.distance_km || 0), 0) const distance = edges.reduce((s, e) => s + (e.distance_km || 0), 0)
const time = edges.reduce((s, e) => s + (e.travel_time_seconds || 0), 0) const time = edges.reduce((s, e) => s + (e.travel_time_seconds || 0), 0)
return { return {
from_uuid: fromDoc?._key ?? null, fromUuid: fromDoc?._key ?? null,
from_name: fromDoc?.name ?? null, fromName: fromDoc?.name ?? null,
from_lat: fromDoc?.latitude ?? null, fromLat: fromDoc?.latitude ?? null,
from_lon: fromDoc?.longitude ?? null, fromLon: fromDoc?.longitude ?? null,
to_uuid: toDoc?._key ?? null, toUuid: toDoc?._key ?? null,
to_name: toDoc?.name ?? null, toName: toDoc?.name ?? null,
to_lat: toDoc?.latitude ?? null, toLat: toDoc?.latitude ?? null,
to_lon: toDoc?.longitude ?? null, toLon: toDoc?.longitude ?? null,
distance_km: distance, distanceKm: distance,
travel_time_seconds: time, travelTimeSeconds: time,
transport_type: transportType, transportType: transportType,
} }
} }
@@ -83,8 +83,8 @@ export function buildRouteFromEdges(pathEdges: [string, string, ArangoDoc][], no
stages.push(buildStage(nodeDocs.get(segmentStart!), nodeDocs.get(lastTo), currentType!, currentEdges)) stages.push(buildStage(nodeDocs.get(segmentStart!), nodeDocs.get(lastTo), currentType!, currentEdges))
return { return {
total_distance_km: stages.reduce((s, st) => s + (st.distance_km || 0), 0), totalDistanceKm: stages.reduce((s, st) => s + (st.distanceKm || 0), 0),
total_time_seconds: stages.reduce((s, st) => s + (st.travel_time_seconds || 0), 0), totalTimeSeconds: stages.reduce((s, st) => s + (st.travelTimeSeconds || 0), 0),
stages, stages,
} }
} }

View File

@@ -8,13 +8,13 @@ const MAX_EXPANSIONS = 20000
export const typeDefs = `#graphql export const typeDefs = `#graphql
type Edge { type Edge {
to_uuid: String toUuid: String
to_name: String toName: String
to_latitude: Float toLatitude: Float
to_longitude: Float toLongitude: Float
distance_km: Float distanceKm: Float
travel_time_seconds: Int travelTimeSeconds: Int
transport_type: String transportType: String
} }
type Node { type Node {
@@ -23,51 +23,51 @@ export const typeDefs = `#graphql
latitude: Float latitude: Float
longitude: Float longitude: Float
country: String country: String
country_code: String countryCode: String
synced_at: String syncedAt: String
transport_types: [String] transportTypes: [String]
edges: [Edge] edges: [Edge]
distance_km: Float distanceKm: Float
} }
type NodeConnections { type NodeConnections {
hub: Node hub: Node
rail_node: Node railNode: Node
auto_edges: [Edge] autoEdges: [Edge]
rail_edges: [Edge] railEdges: [Edge]
} }
type Route { type Route {
distance_km: Float distanceKm: Float
geometry: JSON geometry: JSON
} }
type RouteStage { type RouteStage {
from_uuid: String fromUuid: String
from_name: String fromName: String
from_lat: Float fromLat: Float
from_lon: Float fromLon: Float
to_uuid: String toUuid: String
to_name: String toName: String
to_lat: Float toLat: Float
to_lon: Float toLon: Float
distance_km: Float distanceKm: Float
travel_time_seconds: Int travelTimeSeconds: Int
transport_type: String transportType: String
} }
type RoutePath { type RoutePath {
total_distance_km: Float totalDistanceKm: Float
total_time_seconds: Int totalTimeSeconds: Int
stages: [RouteStage] stages: [RouteStage]
} }
type ProductRouteOption { type ProductRouteOption {
source_uuid: String sourceUuid: String
source_name: String sourceName: String
source_lat: Float sourceLat: Float
source_lon: Float sourceLon: Float
distance_km: Float distanceKm: Float
routes: [RoutePath] routes: [RoutePath]
} }
@@ -76,14 +76,14 @@ export const typeDefs = `#graphql
latitude: Float latitude: Float
longitude: Float longitude: Float
count: Int count: Int
expansion_zoom: Int expansionZoom: Int
name: String name: String
} }
type Product { type Product {
uuid: String uuid: String
name: String name: String
offers_count: Int offersCount: Int
} }
type Supplier { type Supplier {
@@ -91,41 +91,41 @@ export const typeDefs = `#graphql
name: String name: String
latitude: Float latitude: Float
longitude: Float longitude: Float
distance_km: Float distanceKm: Float
} }
type OfferNode { type OfferNode {
uuid: String uuid: String
product_uuid: String productUuid: String
product_name: String productName: String
supplier_uuid: String supplierUuid: String
supplier_name: String supplierName: String
latitude: Float latitude: Float
longitude: Float longitude: Float
country: String country: String
country_code: String countryCode: String
price_per_unit: String pricePerUnit: String
currency: String currency: String
quantity: String quantity: String
unit: String unit: String
distance_km: Float distanceKm: Float
} }
type OfferWithRoute { type OfferWithRoute {
uuid: String uuid: String
product_uuid: String productUuid: String
product_name: String productName: String
supplier_uuid: String supplierUuid: String
supplier_name: String supplierName: String
latitude: Float latitude: Float
longitude: Float longitude: Float
country: String country: String
country_code: String countryCode: String
price_per_unit: String pricePerUnit: String
currency: String currency: String
quantity: String quantity: String
unit: String unit: String
distance_km: Float distanceKm: Float
routes: [RoutePath] routes: [RoutePath]
} }
@@ -133,32 +133,32 @@ export const typeDefs = `#graphql
type Query { type Query {
node(uuid: String!): Node node(uuid: String!): Node
nodes(limit: Int, offset: Int, transport_type: String, country: String, search: String, west: Float, south: Float, east: Float, north: Float): [Node!]! nodes(limit: Int, offset: Int, transportType: String, country: String, search: String, west: Float, south: Float, east: Float, north: Float): [Node!]!
nodes_count(transport_type: String, country: String, west: Float, south: Float, east: Float, north: Float): Int! nodesCount(transportType: String, country: String, west: Float, south: Float, east: Float, north: Float): Int!
hub_countries: [String!]! hubCountries: [String!]!
nearest_nodes(lat: Float!, lon: Float!, limit: Int): [Node!]! nearestNodes(lat: Float!, lon: Float!, limit: Int): [Node!]!
node_connections(uuid: String!, limit_auto: Int, limit_rail: Int): NodeConnections nodeConnections(uuid: String!, limitAuto: Int, limitRail: Int): NodeConnections
auto_route(from_lat: Float!, from_lon: Float!, to_lat: Float!, to_lon: Float!): Route autoRoute(fromLat: Float!, fromLon: Float!, toLat: Float!, toLon: Float!): Route
rail_route(from_lat: Float!, from_lon: Float!, to_lat: Float!, to_lon: Float!): Route railRoute(fromLat: Float!, fromLon: Float!, toLat: Float!, toLon: Float!): Route
clustered_nodes(west: Float!, south: Float!, east: Float!, north: Float!, zoom: Int!, transport_type: String, node_type: String): [ClusterPoint!]! clusteredNodes(west: Float!, south: Float!, east: Float!, north: Float!, zoom: Int!, transportType: String, nodeType: String): [ClusterPoint!]!
products: [Product!]! products: [Product!]!
offers_by_product(product_uuid: String!): [OfferNode!]! offersByProduct(productUuid: String!): [OfferNode!]!
hubs_near_offer(offer_uuid: String!, limit: Int): [Node!]! hubsNearOffer(offerUuid: String!, limit: Int): [Node!]!
suppliers: [Supplier!]! suppliers: [Supplier!]!
products_by_supplier(supplier_uuid: String!): [Product!]! productsBySupplier(supplierUuid: String!): [Product!]!
offers_by_supplier_product(supplier_uuid: String!, product_uuid: String!): [OfferNode!]! offersBySupplierProduct(supplierUuid: String!, productUuid: String!): [OfferNode!]!
products_near_hub(hub_uuid: String!, radius_km: Float): [Product!]! productsNearHub(hubUuid: String!, radiusKm: Float): [Product!]!
suppliers_for_product(product_uuid: String!): [Supplier!]! suppliersForProduct(productUuid: String!): [Supplier!]!
hubs_for_product(product_uuid: String!, radius_km: Float): [Node!]! hubsForProduct(productUuid: String!, radiusKm: Float): [Node!]!
offers_by_hub(hub_uuid: String!, product_uuid: String!, limit: Int): [ProductRouteOption!]! offersByHub(hubUuid: String!, productUuid: String!, limit: Int): [ProductRouteOption!]!
offer_to_hub(offer_uuid: String!, hub_uuid: String!): ProductRouteOption offerToHub(offerUuid: String!, hubUuid: String!): ProductRouteOption
nearest_hubs(lat: Float!, lon: Float!, radius: Float, product_uuid: String, limit: Int): [Node!]! nearestHubs(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Node!]!
nearest_offers(lat: Float!, lon: Float!, radius: Float, product_uuid: String, hub_uuid: String, limit: Int): [OfferWithRoute!]! nearestOffers(lat: Float!, lon: Float!, radius: Float, productUuid: String, hubUuid: String, limit: Int): [OfferWithRoute!]!
nearest_suppliers(lat: Float!, lon: Float!, radius: Float, product_uuid: String, limit: Int): [Supplier!]! nearestSuppliers(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Supplier!]!
route_to_coordinate(offer_uuid: String!, lat: Float!, lon: Float!): ProductRouteOption routeToCoordinate(offerUuid: String!, lat: Float!, lon: Float!): ProductRouteOption
hubs_list(limit: Int, offset: Int, country: String, transport_type: String, west: Float, south: Float, east: Float, north: Float): [Node!]! hubsList(limit: Int, offset: Int, country: String, transportType: String, west: Float, south: Float, east: Float, north: Float): [Node!]!
suppliers_list(limit: Int, offset: Int, country: String, west: Float, south: Float, east: Float, north: Float): [Supplier!]! suppliersList(limit: Int, offset: Int, country: String, west: Float, south: Float, east: Float, north: Float): [Supplier!]!
products_list(limit: Int, offset: Int, west: Float, south: Float, east: Float, north: Float): [Product!]! productsList(limit: Int, offset: Int, west: Float, south: Float, east: Float, north: Float): [Product!]!
} }
` `
@@ -169,30 +169,42 @@ function mapNode(doc: ArangoDoc, includeEdges = false) {
latitude: doc.latitude ?? null, latitude: doc.latitude ?? null,
longitude: doc.longitude ?? null, longitude: doc.longitude ?? null,
country: doc.country ?? null, country: doc.country ?? null,
country_code: doc.country_code ?? null, countryCode: doc.country_code ?? null,
synced_at: doc.synced_at ?? null, syncedAt: doc.synced_at ?? null,
transport_types: doc.transport_types || [], transportTypes: doc.transport_types || [],
edges: includeEdges ? (doc.edges || []) : [], edges: includeEdges ? (doc.edges || []) : [],
distance_km: doc.distance_km ?? null, distanceKm: doc.distance_km ?? null,
} }
} }
function mapOffer(doc: ArangoDoc) { function mapOffer(doc: ArangoDoc) {
return { return {
uuid: doc._key, uuid: doc._key,
product_uuid: doc.product_uuid ?? null, productUuid: doc.product_uuid ?? null,
product_name: doc.product_name ?? null, productName: doc.product_name ?? null,
supplier_uuid: doc.supplier_uuid ?? null, supplierUuid: doc.supplier_uuid ?? null,
supplier_name: doc.supplier_name ?? null, supplierName: doc.supplier_name ?? null,
latitude: doc.latitude ?? null, latitude: doc.latitude ?? null,
longitude: doc.longitude ?? null, longitude: doc.longitude ?? null,
country: doc.country ?? null, country: doc.country ?? null,
country_code: doc.country_code ?? null, countryCode: doc.country_code ?? null,
price_per_unit: doc.price_per_unit ?? null, pricePerUnit: doc.price_per_unit ?? null,
currency: doc.currency ?? null, currency: doc.currency ?? null,
quantity: doc.quantity ?? null, quantity: doc.quantity ?? null,
unit: doc.unit ?? null, unit: doc.unit ?? null,
distance_km: doc.distance_km ?? null, distanceKm: doc.distance_km ?? null,
}
}
function mapEdge(doc: ArangoDoc) {
return {
toUuid: doc.to_uuid ?? doc._key ?? null,
toName: doc.to_name ?? doc.name ?? null,
toLatitude: doc.to_latitude ?? doc.latitude ?? null,
toLongitude: doc.to_longitude ?? doc.longitude ?? null,
distanceKm: doc.distance_km ?? null,
travelTimeSeconds: doc.travel_time_seconds ?? null,
transportType: doc.transport_type ?? null,
} }
} }
@@ -314,11 +326,11 @@ async function resolveOfferToHubInternal(offerUuid: string, hubUuid: string): Pr
} }
return { return {
source_uuid: offerUuid, sourceUuid: offerUuid,
source_name: offer.name || offer.product_name, sourceName: offer.name || offer.product_name,
source_lat: offerLat, sourceLat: offerLat,
source_lon: offerLon, sourceLon: offerLon,
distance_km: dm, distanceKm: dm,
routes: route ? [route] : [], routes: route ? [route] : [],
} }
} }
@@ -360,7 +372,7 @@ export const resolvers = {
}, },
Product: { Product: {
offers_count: async (parent: { uuid: string }) => { offersCount: async (parent: { uuid: string }) => {
const db = getDb() const db = getDb()
try { try {
const cursor = await db.query(` const cursor = await db.query(`
@@ -399,10 +411,10 @@ export const resolvers = {
`, { from_id: `nodes/${args.uuid}` }) `, { from_id: `nodes/${args.uuid}` })
const edges = await cursor.all() const edges = await cursor.all()
return { ...mapNode(node, true), edges } return { ...mapNode(node, true), edges: edges.map(mapEdge) }
}, },
nodes: async (_: unknown, args: { limit?: number; offset?: number; transport_type?: string; country?: string; search?: string; west?: number; south?: number; east?: number; north?: number }) => { nodes: async (_: unknown, args: { limit?: number; offset?: number; transportType?: string; country?: string; search?: string; west?: number; south?: number; east?: number; north?: number }) => {
const db = getDb() const db = getDb()
const bounds = boundsFilter(args) const bounds = boundsFilter(args)
@@ -418,7 +430,7 @@ export const resolvers = {
LIMIT @offset, @limit LIMIT @offset, @limit
RETURN node RETURN node
`, { `, {
transport_type: args.transport_type ?? null, transport_type: args.transportType ?? null,
country: args.country ?? null, country: args.country ?? null,
search: args.search ?? null, search: args.search ?? null,
offset: args.offset ?? 0, offset: args.offset ?? 0,
@@ -429,7 +441,7 @@ export const resolvers = {
return nodes.map((n: ArangoDoc) => mapNode(n)) return nodes.map((n: ArangoDoc) => mapNode(n))
}, },
nodes_count: async (_: unknown, args: { transport_type?: string; country?: string; west?: number; south?: number; east?: number; north?: number }) => { nodesCount: async (_: unknown, args: { transportType?: string; country?: string; west?: number; south?: number; east?: number; north?: number }) => {
const db = getDb() const db = getDb()
const bounds = boundsFilter(args) const bounds = boundsFilter(args)
@@ -442,11 +454,11 @@ export const resolvers = {
${bounds.filter} ${bounds.filter}
COLLECT WITH COUNT INTO length COLLECT WITH COUNT INTO length
RETURN length RETURN length
`, { transport_type: args.transport_type ?? null, country: args.country ?? null, ...bounds.vars }) `, { transport_type: args.transportType ?? null, country: args.country ?? null, ...bounds.vars })
return (await cursor.next()) ?? 0 return (await cursor.next()) ?? 0
}, },
hub_countries: async () => { hubCountries: async () => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR node IN nodes FOR node IN nodes
@@ -459,7 +471,7 @@ export const resolvers = {
return cursor.all() return cursor.all()
}, },
nearest_nodes: async (_: unknown, args: { lat: number; lon: number; limit?: number }) => { nearestNodes: async (_: unknown, args: { lat: number; lon: number; limit?: number }) => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR node IN nodes FOR node IN nodes
@@ -474,7 +486,7 @@ export const resolvers = {
return nodes.map((n: ArangoDoc) => mapNode(n)) return nodes.map((n: ArangoDoc) => mapNode(n))
}, },
node_connections: async (_: unknown, args: { uuid: string; limit_auto?: number; limit_rail?: number }) => { nodeConnections: async (_: unknown, args: { uuid: string; limitAuto?: number; limitRail?: number }) => {
const db = getDb() const db = getDb()
const nodesCol = db.collection('nodes') const nodesCol = db.collection('nodes')
const hub = await nodesCol.document(args.uuid).catch(() => null) const hub = await nodesCol.document(args.uuid).catch(() => null)
@@ -524,8 +536,8 @@ export const resolvers = {
hub_lat: hub.latitude, hub_lat: hub.latitude,
hub_lon: hub.longitude, hub_lon: hub.longitude,
hub_has_rail: (hub.transport_types || []).includes('rail'), hub_has_rail: (hub.transport_types || []).includes('rail'),
limit_auto: args.limit_auto ?? 12, limit_auto: args.limitAuto ?? 12,
limit_rail: args.limit_rail ?? 12, limit_rail: args.limitRail ?? 12,
}) })
const result = await cursor.next() const result = await cursor.next()
@@ -533,16 +545,16 @@ export const resolvers = {
return { return {
hub: result.hub ? mapNode(result.hub) : null, hub: result.hub ? mapNode(result.hub) : null,
rail_node: result.rail_node ? mapNode(result.rail_node) : null, railNode: result.rail_node ? mapNode(result.rail_node) : null,
auto_edges: result.auto_edges || [], autoEdges: (result.auto_edges || []).map(mapEdge),
rail_edges: result.rail_edges || [], railEdges: (result.rail_edges || []).map(mapEdge),
} }
}, },
auto_route: async (_: unknown, args: { from_lat: number; from_lon: number; to_lat: number; to_lon: number }) => { autoRoute: async (_: unknown, args: { fromLat: number; fromLon: number; toLat: number; toLon: number }) => {
const url = new URL('/route', GRAPHHOPPER_URL) const url = new URL('/route', GRAPHHOPPER_URL)
url.searchParams.append('point', `${args.from_lat},${args.from_lon}`) url.searchParams.append('point', `${args.fromLat},${args.fromLon}`)
url.searchParams.append('point', `${args.to_lat},${args.to_lon}`) url.searchParams.append('point', `${args.toLat},${args.toLon}`)
url.searchParams.append('profile', 'car') url.searchParams.append('profile', 'car')
url.searchParams.append('instructions', 'false') url.searchParams.append('instructions', 'false')
url.searchParams.append('calc_points', 'true') url.searchParams.append('calc_points', 'true')
@@ -553,16 +565,16 @@ export const resolvers = {
const data = await res.json() as ArangoDoc const data = await res.json() as ArangoDoc
if (data.paths?.length > 0) { if (data.paths?.length > 0) {
const path = data.paths[0] const path = data.paths[0]
return { distance_km: Math.round((path.distance || 0) / 10) / 100, geometry: path.points?.coordinates || [] } return { distanceKm: Math.round((path.distance || 0) / 10) / 100, geometry: path.points?.coordinates || [] }
} }
} catch (e) { console.error('GraphHopper request failed:', e) } } catch (e) { console.error('GraphHopper request failed:', e) }
return null return null
}, },
rail_route: async (_: unknown, args: { from_lat: number; from_lon: number; to_lat: number; to_lon: number }) => { railRoute: async (_: unknown, args: { fromLat: number; fromLon: number; toLat: number; toLon: number }) => {
const url = new URL('/route', OPENRAILROUTING_URL) const url = new URL('/route', OPENRAILROUTING_URL)
url.searchParams.append('point', `${args.from_lat},${args.from_lon}`) url.searchParams.append('point', `${args.fromLat},${args.fromLon}`)
url.searchParams.append('point', `${args.to_lat},${args.to_lon}`) url.searchParams.append('point', `${args.toLat},${args.toLon}`)
url.searchParams.append('profile', 'all_tracks') url.searchParams.append('profile', 'all_tracks')
url.searchParams.append('calc_points', 'true') url.searchParams.append('calc_points', 'true')
url.searchParams.append('points_encoded', 'false') url.searchParams.append('points_encoded', 'false')
@@ -572,14 +584,22 @@ export const resolvers = {
const data = await res.json() as ArangoDoc const data = await res.json() as ArangoDoc
if (data.paths?.length > 0) { if (data.paths?.length > 0) {
const path = data.paths[0] const path = data.paths[0]
return { distance_km: Math.round((path.distance || 0) / 10) / 100, geometry: path.points?.coordinates || [] } return { distanceKm: Math.round((path.distance || 0) / 10) / 100, geometry: path.points?.coordinates || [] }
} }
} catch (e) { console.error('OpenRailRouting request failed:', e) } } catch (e) { console.error('OpenRailRouting request failed:', e) }
return null return null
}, },
clustered_nodes: async (_: unknown, args: { west: number; south: number; east: number; north: number; zoom: number; transport_type?: string; node_type?: string }) => { clusteredNodes: async (_: unknown, args: { west: number; south: number; east: number; north: number; zoom: number; transportType?: string; nodeType?: string }) => {
return getClusteredNodes(args.west, args.south, args.east, args.north, args.zoom, args.transport_type, args.node_type) const points = await getClusteredNodes(args.west, args.south, args.east, args.north, args.zoom, args.transportType, args.nodeType)
return points.map((p: ArangoDoc) => ({
id: p.id,
latitude: p.latitude,
longitude: p.longitude,
count: p.count,
expansionZoom: p.expansion_zoom ?? p.expansionZoom ?? null,
name: p.name ?? null,
}))
}, },
products: async () => { products: async () => {
@@ -595,22 +615,22 @@ export const resolvers = {
return cursor.all() return cursor.all()
}, },
offers_by_product: async (_: unknown, args: { product_uuid: string }) => { offersByProduct: async (_: unknown, args: { productUuid: string }) => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR node IN nodes FOR node IN nodes
FILTER node.node_type == 'offer' FILTER node.node_type == 'offer'
FILTER node.product_uuid == @product_uuid FILTER node.product_uuid == @product_uuid
RETURN node RETURN node
`, { product_uuid: args.product_uuid }) `, { product_uuid: args.productUuid })
const nodes = await cursor.all() const nodes = await cursor.all()
return nodes.map(mapOffer) return nodes.map(mapOffer)
}, },
hubs_near_offer: async (_: unknown, args: { offer_uuid: string; limit?: number }) => { hubsNearOffer: async (_: unknown, args: { offerUuid: string; limit?: number }) => {
const db = getDb() const db = getDb()
const nodesCol = db.collection('nodes') const nodesCol = db.collection('nodes')
const offer = await nodesCol.document(args.offer_uuid).catch(() => null) const offer = await nodesCol.document(args.offerUuid).catch(() => null)
if (!offer || offer.latitude == null || offer.longitude == null) return [] if (!offer || offer.latitude == null || offer.longitude == null) return []
const cursor = await db.query(` const cursor = await db.query(`
@@ -639,7 +659,7 @@ export const resolvers = {
return cursor.all() return cursor.all()
}, },
products_by_supplier: async (_: unknown, args: { supplier_uuid: string }) => { productsBySupplier: async (_: unknown, args: { supplierUuid: string }) => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR node IN nodes FOR node IN nodes
@@ -649,11 +669,11 @@ export const resolvers = {
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
RETURN { uuid: product_uuid, name: first_offer.product_name } RETURN { uuid: product_uuid, name: first_offer.product_name }
`, { supplier_uuid: args.supplier_uuid }) `, { supplier_uuid: args.supplierUuid })
return cursor.all() return cursor.all()
}, },
offers_by_supplier_product: async (_: unknown, args: { supplier_uuid: string; product_uuid: string }) => { offersBySupplierProduct: async (_: unknown, args: { supplierUuid: string; productUuid: string }) => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR node IN nodes FOR node IN nodes
@@ -661,15 +681,15 @@ export const resolvers = {
FILTER node.supplier_uuid == @supplier_uuid FILTER node.supplier_uuid == @supplier_uuid
FILTER node.product_uuid == @product_uuid FILTER node.product_uuid == @product_uuid
RETURN node RETURN node
`, { supplier_uuid: args.supplier_uuid, product_uuid: args.product_uuid }) `, { supplier_uuid: args.supplierUuid, product_uuid: args.productUuid })
const nodes = await cursor.all() const nodes = await cursor.all()
return nodes.map(mapOffer) return nodes.map(mapOffer)
}, },
products_near_hub: async (_: unknown, args: { hub_uuid: string; radius_km?: number }) => { productsNearHub: async (_: unknown, args: { hubUuid: string; radiusKm?: number }) => {
const db = getDb() const db = getDb()
const nodesCol = db.collection('nodes') const nodesCol = db.collection('nodes')
const hub = await nodesCol.document(args.hub_uuid).catch(() => null) const hub = await nodesCol.document(args.hubUuid).catch(() => null)
if (!hub || hub.latitude == null || hub.longitude == null) return [] if (!hub || hub.latitude == null || hub.longitude == null) return []
const cursor = await db.query(` const cursor = await db.query(`
@@ -682,11 +702,11 @@ export const resolvers = {
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
RETURN { uuid: product_uuid, name: first_offer.product_name } RETURN { uuid: product_uuid, name: first_offer.product_name }
`, { lat: hub.latitude, lon: hub.longitude, radius_km: args.radius_km ?? 500 }) `, { lat: hub.latitude, lon: hub.longitude, radius_km: args.radiusKm ?? 500 })
return cursor.all() return cursor.all()
}, },
suppliers_for_product: async (_: unknown, args: { product_uuid: string }) => { suppliersForProduct: async (_: unknown, args: { productUuid: string }) => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR node IN nodes FOR node IN nodes
@@ -695,11 +715,11 @@ export const resolvers = {
FILTER node.supplier_uuid != null FILTER node.supplier_uuid != null
COLLECT supplier_uuid = node.supplier_uuid COLLECT supplier_uuid = node.supplier_uuid
RETURN { uuid: supplier_uuid } RETURN { uuid: supplier_uuid }
`, { product_uuid: args.product_uuid }) `, { product_uuid: args.productUuid })
return cursor.all() return cursor.all()
}, },
hubs_for_product: async (_: unknown, args: { product_uuid: string; radius_km?: number }) => { hubsForProduct: async (_: unknown, args: { productUuid: string; radiusKm?: number }) => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR offer IN nodes FOR offer IN nodes
@@ -719,17 +739,17 @@ export const resolvers = {
uuid: hub_uuid, name: hub_name, latitude: hub_lat, longitude: hub_lon, uuid: hub_uuid, name: hub_name, latitude: hub_lat, longitude: hub_lon,
country: hub_country, country_code: hub_country_code, transport_types: hub_transport country: hub_country, country_code: hub_country_code, transport_types: hub_transport
} }
`, { product_uuid: args.product_uuid, radius_km: args.radius_km ?? 500 }) `, { product_uuid: args.productUuid, radius_km: args.radiusKm ?? 500 })
const hubs = await cursor.all() const hubs = await cursor.all()
return hubs.map((h: ArangoDoc) => ({ ...mapNode(h), uuid: h.uuid })) return hubs.map((h: ArangoDoc) => ({ ...mapNode(h), uuid: h.uuid }))
}, },
offers_by_hub: async (_: unknown, args: { hub_uuid: string; product_uuid: string; limit?: number }) => { offersByHub: async (_: unknown, args: { hubUuid: string; productUuid: string; limit?: number }) => {
const db = getDb() const db = getDb()
await ensureGraph() await ensureGraph()
const nodesCol = db.collection('nodes') const nodesCol = db.collection('nodes')
const hub = await nodesCol.document(args.hub_uuid).catch(() => null) const hub = await nodesCol.document(args.hubUuid).catch(() => null)
if (!hub || hub.latitude == null || hub.longitude == null) return [] if (!hub || hub.latitude == null || hub.longitude == null) return []
const hubLat = hub.latitude as number const hubLat = hub.latitude as number
@@ -737,11 +757,11 @@ export const resolvers = {
const limit = args.limit ?? 10 const limit = args.limit ?? 10
// Priority queue: [cost, seq, nodeKey, phase] // Priority queue: [cost, seq, nodeKey, phase]
const queue: [number, number, string, Phase][] = [[0, 0, args.hub_uuid, 'end_auto']] const queue: [number, number, string, Phase][] = [[0, 0, args.hubUuid, 'end_auto']]
let counter = 0 let counter = 0
const visited = new Map<string, number>() const visited = new Map<string, number>()
const predecessors = new Map<string, [string, ArangoDoc]>() const predecessors = new Map<string, [string, ArangoDoc]>()
const nodeDocs = new Map<string, ArangoDoc>([[args.hub_uuid, hub]]) const nodeDocs = new Map<string, ArangoDoc>([[args.hubUuid, hub]])
const foundRoutes: ArangoDoc[] = [] const foundRoutes: ArangoDoc[] = []
let expansions = 0 let expansions = 0
@@ -753,7 +773,7 @@ export const resolvers = {
if (visited.has(stateKey) && cost > visited.get(stateKey)!) continue if (visited.has(stateKey) && cost > visited.get(stateKey)!) continue
const nodeDoc = nodeDocs.get(nodeKey) const nodeDoc = nodeDocs.get(nodeKey)
if (nodeDoc && nodeDoc.product_uuid === args.product_uuid) { if (nodeDoc && nodeDoc.product_uuid === args.productUuid) {
const pathEdges: [string, string, ArangoDoc][] = [] const pathEdges: [string, string, ArangoDoc][] = []
let curState = stateKey let curState = stateKey
let curKey = nodeKey let curKey = nodeKey
@@ -772,11 +792,11 @@ export const resolvers = {
if (srcLat != null && srcLon != null) dm = distanceKm(srcLat, srcLon, hubLat, hubLon) if (srcLat != null && srcLon != null) dm = distanceKm(srcLat, srcLon, hubLat, hubLon)
foundRoutes.push({ foundRoutes.push({
source_uuid: nodeKey, sourceUuid: nodeKey,
source_name: nodeDoc.name || nodeDoc.product_name, sourceName: nodeDoc.name || nodeDoc.product_name,
source_lat: srcLat, sourceLat: srcLat,
source_lon: srcLon, sourceLon: srcLon,
distance_km: dm, distanceKm: dm,
routes: route ? [route] : [], routes: route ? [route] : [],
}) })
continue continue
@@ -810,11 +830,11 @@ export const resolvers = {
return foundRoutes return foundRoutes
}, },
offer_to_hub: async (_: unknown, args: { offer_uuid: string; hub_uuid: string }) => { offerToHub: async (_: unknown, args: { offerUuid: string; hubUuid: string }) => {
return resolveOfferToHubInternal(args.offer_uuid, args.hub_uuid) return resolveOfferToHubInternal(args.offerUuid, args.hubUuid)
}, },
nearest_hubs: async (_: unknown, args: { lat: number; lon: number; radius?: number; product_uuid?: string; limit?: number }) => { nearestHubs: async (_: unknown, args: { lat: number; lon: number; radius?: number; productUuid?: string; limit?: number }) => {
const db = getDb() const db = getDb()
const radius = args.radius ?? 1000 const radius = args.radius ?? 1000
const limit = args.limit ?? 12 const limit = args.limit ?? 12
@@ -822,7 +842,7 @@ export const resolvers = {
let aql: string let aql: string
let bindVars: Record<string, unknown> let bindVars: Record<string, unknown>
if (args.product_uuid) { if (args.productUuid) {
aql = ` aql = `
FOR offer IN nodes FOR offer IN nodes
FILTER offer.node_type == 'offer' FILTER offer.node_type == 'offer'
@@ -843,7 +863,7 @@ export const resolvers = {
LIMIT @limit LIMIT @limit
RETURN MERGE(first_hub, {_key: hub_uuid, distance_km: hub_dist}) RETURN MERGE(first_hub, {_key: hub_uuid, distance_km: hub_dist})
` `
bindVars = { lat: args.lat, lon: args.lon, radius, product_uuid: args.product_uuid, limit } bindVars = { lat: args.lat, lon: args.lon, radius, product_uuid: args.productUuid, limit }
} else { } else {
aql = ` aql = `
FOR hub IN nodes FOR hub IN nodes
@@ -864,7 +884,7 @@ export const resolvers = {
return hubs.map((n: ArangoDoc) => mapNode(n)) return hubs.map((n: ArangoDoc) => mapNode(n))
}, },
nearest_offers: async (_: unknown, args: { lat: number; lon: number; radius?: number; product_uuid?: string; hub_uuid?: string; limit?: number }) => { nearestOffers: async (_: unknown, args: { lat: number; lon: number; radius?: number; productUuid?: string; hubUuid?: string; limit?: number }) => {
const db = getDb() const db = getDb()
await ensureGraph() await ensureGraph()
const radius = args.radius ?? 500 const radius = args.radius ?? 500
@@ -876,7 +896,7 @@ export const resolvers = {
FILTER offer.product_uuid != null FILTER offer.product_uuid != null
FILTER offer.latitude != null AND offer.longitude != null FILTER offer.latitude != null AND offer.longitude != null
` `
if (args.product_uuid) aql += ` FILTER offer.product_uuid == @product_uuid\n` if (args.productUuid) aql += ` FILTER offer.product_uuid == @product_uuid\n`
aql += ` aql += `
LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000 LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
FILTER dist <= @radius FILTER dist <= @radius
@@ -886,7 +906,7 @@ export const resolvers = {
` `
const bindVars: Record<string, unknown> = { lat: args.lat, lon: args.lon, radius, limit } const bindVars: Record<string, unknown> = { lat: args.lat, lon: args.lon, radius, limit }
if (args.product_uuid) bindVars.product_uuid = args.product_uuid if (args.productUuid) bindVars.product_uuid = args.productUuid
const cursor = await db.query(aql, bindVars) const cursor = await db.query(aql, bindVars)
const offerNodes = await cursor.all() const offerNodes = await cursor.all()
@@ -894,8 +914,8 @@ export const resolvers = {
const offers = [] const offers = []
for (const node of offerNodes) { for (const node of offerNodes) {
let routes: RoutePath[] = [] let routes: RoutePath[] = []
if (args.hub_uuid) { if (args.hubUuid) {
const routeResult = await resolveOfferToHubInternal(node._key, args.hub_uuid) const routeResult = await resolveOfferToHubInternal(node._key, args.hubUuid)
if (routeResult?.routes) routes = routeResult.routes as RoutePath[] if (routeResult?.routes) routes = routeResult.routes as RoutePath[]
} }
offers.push({ ...mapOffer(node), routes }) offers.push({ ...mapOffer(node), routes })
@@ -903,7 +923,7 @@ export const resolvers = {
return offers return offers
}, },
nearest_suppliers: async (_: unknown, args: { lat: number; lon: number; radius?: number; product_uuid?: string; limit?: number }) => { nearestSuppliers: async (_: unknown, args: { lat: number; lon: number; radius?: number; productUuid?: string; limit?: number }) => {
const db = getDb() const db = getDb()
const radius = args.radius ?? 1000 const radius = args.radius ?? 1000
const limit = args.limit ?? 12 const limit = args.limit ?? 12
@@ -914,7 +934,7 @@ export const resolvers = {
FILTER offer.supplier_uuid != null FILTER offer.supplier_uuid != null
FILTER offer.latitude != null AND offer.longitude != null FILTER offer.latitude != null AND offer.longitude != null
` `
if (args.product_uuid) aql += ` FILTER offer.product_uuid == @product_uuid\n` if (args.productUuid) aql += ` FILTER offer.product_uuid == @product_uuid\n`
aql += ` aql += `
LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000 LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
FILTER dist <= @radius FILTER dist <= @radius
@@ -934,18 +954,18 @@ export const resolvers = {
name: supplier_node != null ? supplier_node.name : first_offer.supplier_name, name: supplier_node != null ? supplier_node.name : first_offer.supplier_name,
latitude: supplier_node != null ? supplier_node.latitude : first_offer.latitude, latitude: supplier_node != null ? supplier_node.latitude : first_offer.latitude,
longitude: supplier_node != null ? supplier_node.longitude : first_offer.longitude, longitude: supplier_node != null ? supplier_node.longitude : first_offer.longitude,
distance_km: supplier_dist distanceKm: supplier_dist
} }
` `
const bindVars: Record<string, unknown> = { lat: args.lat, lon: args.lon, radius, limit } const bindVars: Record<string, unknown> = { lat: args.lat, lon: args.lon, radius, limit }
if (args.product_uuid) bindVars.product_uuid = args.product_uuid if (args.productUuid) bindVars.product_uuid = args.productUuid
const cursor = await db.query(aql, bindVars) const cursor = await db.query(aql, bindVars)
return cursor.all() return cursor.all()
}, },
route_to_coordinate: async (_: unknown, args: { offer_uuid: string; lat: number; lon: number }) => { routeToCoordinate: async (_: unknown, args: { offerUuid: string; lat: number; lon: number }) => {
const db = getDb() const db = getDb()
const cursor = await db.query(` const cursor = await db.query(`
FOR hub IN nodes FOR hub IN nodes
@@ -960,10 +980,10 @@ export const resolvers = {
const hubs = await cursor.all() const hubs = await cursor.all()
if (!hubs.length) return null if (!hubs.length) return null
return resolveOfferToHubInternal(args.offer_uuid, hubs[0]._key) return resolveOfferToHubInternal(args.offerUuid, hubs[0]._key)
}, },
hubs_list: async (_: unknown, args: { limit?: number; offset?: number; country?: string; transport_type?: string; west?: number; south?: number; east?: number; north?: number }) => { hubsList: async (_: unknown, args: { limit?: number; offset?: number; country?: string; transportType?: string; west?: number; south?: number; east?: number; north?: number }) => {
const db = getDb() const db = getDb()
const bounds = boundsFilter(args) const bounds = boundsFilter(args)
@@ -978,12 +998,12 @@ export const resolvers = {
SORT node.name ASC SORT node.name ASC
LIMIT @offset, @limit LIMIT @offset, @limit
RETURN node RETURN node
`, { transport_type: args.transport_type ?? null, country: args.country ?? null, offset: args.offset ?? 0, limit: args.limit ?? 50, ...bounds.vars }) `, { transport_type: args.transportType ?? null, country: args.country ?? null, offset: args.offset ?? 0, limit: args.limit ?? 50, ...bounds.vars })
const nodes = await cursor.all() const nodes = await cursor.all()
return nodes.map((n: ArangoDoc) => mapNode(n)) return nodes.map((n: ArangoDoc) => mapNode(n))
}, },
suppliers_list: async (_: unknown, args: { limit?: number; offset?: number; country?: string; west?: number; south?: number; east?: number; north?: number }) => { suppliersList: async (_: unknown, args: { limit?: number; offset?: number; country?: string; west?: number; south?: number; east?: number; north?: number }) => {
const db = getDb() const db = getDb()
const bounds = boundsFilter(args) const bounds = boundsFilter(args)
@@ -1002,11 +1022,11 @@ export const resolvers = {
name: n.name ?? null, name: n.name ?? null,
latitude: n.latitude ?? null, latitude: n.latitude ?? null,
longitude: n.longitude ?? null, longitude: n.longitude ?? null,
distance_km: null, distanceKm: null,
})) }))
}, },
products_list: async (_: unknown, args: { limit?: number; offset?: number; west?: number; south?: number; east?: number; north?: number }) => { productsList: async (_: unknown, args: { limit?: number; offset?: number; west?: number; south?: number; east?: number; north?: number }) => {
const db = getDb() const db = getDb()
const bounds = boundsFilter(args) const bounds = boundsFilter(args)