Match landing stories to catalog navigation
Build and deploy Docker image / build (push) Successful in 1m10s
Build and deploy Docker image / build (push) Successful in 1m10s
This commit is contained in:
+1
-1
Submodule graphql-contracts updated: ca08538d38...e96e5afd01
+142
-44
@@ -127,9 +127,31 @@ export const typeDefs = `#graphql
|
|||||||
distanceKm: Float
|
distanceKm: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LandingNetworkOffer {
|
||||||
|
uuid: String!
|
||||||
|
productUuid: String!
|
||||||
|
productName: String!
|
||||||
|
supplierUuid: String!
|
||||||
|
supplierName: String!
|
||||||
|
country: String
|
||||||
|
latitude: Float!
|
||||||
|
longitude: Float!
|
||||||
|
distanceKm: Float
|
||||||
|
pricePerUnit: String
|
||||||
|
currency: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type LandingNetworkProduct {
|
||||||
|
uuid: String!
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
type LandingNetworkStory {
|
type LandingNetworkStory {
|
||||||
focus: LandingNetworkPoint!
|
focus: LandingNetworkPoint!
|
||||||
neighbors: [LandingNetworkPoint!]!
|
offers: [LandingNetworkOffer!]!
|
||||||
|
hubs: [LandingNetworkPoint!]!
|
||||||
|
products: [LandingNetworkProduct!]!
|
||||||
|
suppliers: [LandingNetworkPoint!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type OfferNode {
|
type OfferNode {
|
||||||
@@ -197,7 +219,7 @@ export const typeDefs = `#graphql
|
|||||||
nearestHubs(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Node!]!
|
nearestHubs(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Node!]!
|
||||||
nearestOffers(lat: Float!, lon: Float!, radius: Float, productUuid: String, hubUuid: String, limit: Int): [OfferWithRoute!]!
|
nearestOffers(lat: Float!, lon: Float!, radius: Float, productUuid: String, hubUuid: String, limit: Int): [OfferWithRoute!]!
|
||||||
nearestSuppliers(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Supplier!]!
|
nearestSuppliers(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Supplier!]!
|
||||||
landingNetworkStory(focusKind: LandingNetworkKind!, neighborLimit: Int): LandingNetworkStory!
|
landingNetworkStory(focusKind: LandingNetworkKind!, itemLimit: Int): LandingNetworkStory!
|
||||||
routeToCoordinate(offerUuid: String!, lat: Float!, lon: Float!): ProductRouteOption
|
routeToCoordinate(offerUuid: String!, lat: Float!, lon: Float!): ProductRouteOption
|
||||||
hubsList(limit: Int, offset: Int, country: String, transportType: 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!]!
|
||||||
suppliersList(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!]!
|
||||||
@@ -272,30 +294,87 @@ function mapLandingNetworkPoint(doc: ArangoDoc, kind: LandingNetworkKind) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveLandingNetworkStory(focusKind: LandingNetworkKind, neighborLimit?: number) {
|
function mapLandingNetworkOffer(doc: ArangoDoc) {
|
||||||
|
return {
|
||||||
|
uuid: doc._key,
|
||||||
|
productUuid: doc.product_uuid,
|
||||||
|
productName: doc.product_name,
|
||||||
|
supplierUuid: doc.supplier_uuid,
|
||||||
|
supplierName: doc.supplier_name,
|
||||||
|
country: doc.country ?? null,
|
||||||
|
latitude: doc.latitude,
|
||||||
|
longitude: doc.longitude,
|
||||||
|
distanceKm: doc.distance_km ?? null,
|
||||||
|
pricePerUnit: doc.price_per_unit ?? null,
|
||||||
|
currency: doc.currency ?? null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function landingProductsFromOffers(offers: ArangoDoc[], limit: number) {
|
||||||
|
const products = new Map<string, { uuid: string; name: string }>()
|
||||||
|
for (const offer of offers) {
|
||||||
|
if (!products.has(offer.product_uuid)) {
|
||||||
|
products.set(offer.product_uuid, { uuid: offer.product_uuid, name: offer.product_name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...products.values()].slice(0, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
function landingSuppliersFromOffers(offers: ArangoDoc[], limit: number) {
|
||||||
|
const suppliers = new Map<string, ReturnType<typeof mapLandingNetworkPoint>>()
|
||||||
|
for (const offer of offers) {
|
||||||
|
if (!suppliers.has(offer.supplier_uuid)) {
|
||||||
|
suppliers.set(offer.supplier_uuid, mapLandingNetworkPoint({
|
||||||
|
_key: offer.supplier_uuid,
|
||||||
|
name: offer.supplier_name,
|
||||||
|
country: offer.country,
|
||||||
|
latitude: offer.latitude,
|
||||||
|
longitude: offer.longitude,
|
||||||
|
distance_km: offer.distance_km,
|
||||||
|
}, 'SUPPLIER'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...suppliers.values()].slice(0, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveLandingNetworkStory(focusKind: LandingNetworkKind, itemLimit?: number) {
|
||||||
const db = getDb()
|
const db = getDb()
|
||||||
const limit = Math.max(1, Math.min(neighborLimit ?? 3, 6))
|
const limit = Math.max(1, Math.min(itemLimit ?? 3, 6))
|
||||||
const focusCursor = await db.query(focusKind === 'HUB' ? `
|
|
||||||
FOR hub IN nodes
|
if (focusKind === 'SUPPLIER') {
|
||||||
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
const focusCursor = await db.query(`
|
||||||
FILTER hub.product_uuid == null
|
|
||||||
FILTER hub.latitude != null AND hub.longitude != null
|
|
||||||
SORT RAND()
|
|
||||||
LIMIT 1
|
|
||||||
RETURN hub
|
|
||||||
` : `
|
|
||||||
FOR supplier IN nodes
|
FOR supplier IN nodes
|
||||||
FILTER supplier.node_type == 'supplier'
|
FILTER supplier.node_type == 'supplier'
|
||||||
FILTER supplier.latitude != null AND supplier.longitude != null
|
FILTER supplier.latitude != null AND supplier.longitude != null
|
||||||
|
FILTER LENGTH(
|
||||||
|
FOR offer IN nodes
|
||||||
|
FILTER offer.node_type == 'offer'
|
||||||
|
FILTER offer.supplier_uuid == supplier._key
|
||||||
|
FILTER offer.product_uuid != null AND offer.product_name != null
|
||||||
|
LIMIT 1
|
||||||
|
RETURN 1
|
||||||
|
) > 0
|
||||||
SORT RAND()
|
SORT RAND()
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
RETURN supplier
|
RETURN supplier
|
||||||
`)
|
`)
|
||||||
const focus = await focusCursor.next() as ArangoDoc | undefined
|
const focus = await focusCursor.next() as ArangoDoc | undefined
|
||||||
if (!focus) throw new Error(`No ${focusKind.toLowerCase()} is available for the landing network story`)
|
if (!focus) throw new Error('No supplier with offers is available for the landing network story')
|
||||||
|
|
||||||
if (focusKind === 'SUPPLIER') {
|
const offersCursor = await db.query(`
|
||||||
const neighborsCursor = await db.query(`
|
FOR offer IN nodes
|
||||||
|
FILTER offer.node_type == 'offer'
|
||||||
|
FILTER offer.supplier_uuid == @supplier_uuid
|
||||||
|
FILTER offer.product_uuid != null AND offer.product_name != null
|
||||||
|
FILTER offer.supplier_name != null
|
||||||
|
FILTER offer.latitude != null AND offer.longitude != null
|
||||||
|
SORT RAND()
|
||||||
|
LIMIT @limit
|
||||||
|
RETURN MERGE(offer, {distance_km: 0})
|
||||||
|
`, { supplier_uuid: focus._key, limit })
|
||||||
|
const offers = await offersCursor.all() as ArangoDoc[]
|
||||||
|
|
||||||
|
const hubsCursor = await db.query(`
|
||||||
FOR hub IN nodes
|
FOR hub IN nodes
|
||||||
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||||
FILTER hub.product_uuid == null
|
FILTER hub.product_uuid == null
|
||||||
@@ -305,43 +384,62 @@ async function resolveLandingNetworkStory(focusKind: LandingNetworkKind, neighbo
|
|||||||
LIMIT @limit
|
LIMIT @limit
|
||||||
RETURN MERGE(hub, {distance_km: dist})
|
RETURN MERGE(hub, {distance_km: dist})
|
||||||
`, { lat: focus.latitude, lon: focus.longitude, limit })
|
`, { lat: focus.latitude, lon: focus.longitude, limit })
|
||||||
const neighbors = await neighborsCursor.all()
|
const hubs = await hubsCursor.all()
|
||||||
return {
|
return {
|
||||||
focus: mapLandingNetworkPoint(focus, 'SUPPLIER'),
|
focus: mapLandingNetworkPoint(focus, 'SUPPLIER'),
|
||||||
neighbors: neighbors.map((neighbor: ArangoDoc) => mapLandingNetworkPoint(neighbor, 'HUB')),
|
offers: offers.map(mapLandingNetworkOffer),
|
||||||
|
hubs: hubs.map((hub: ArangoDoc) => mapLandingNetworkPoint(hub, 'HUB')),
|
||||||
|
products: [],
|
||||||
|
suppliers: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const neighborsCursor = await db.query(`
|
const sourceOfferCursor = await db.query(`
|
||||||
FOR offer IN nodes
|
FOR offer IN nodes
|
||||||
FILTER offer.node_type == 'offer'
|
FILTER offer.node_type == 'offer'
|
||||||
FILTER offer.supplier_uuid != null
|
FILTER offer.product_uuid != null AND offer.product_name != null
|
||||||
|
FILTER offer.supplier_uuid != null AND offer.supplier_name != null
|
||||||
|
FILTER offer.latitude != null AND offer.longitude != null
|
||||||
|
SORT RAND()
|
||||||
|
LIMIT 1
|
||||||
|
RETURN offer
|
||||||
|
`)
|
||||||
|
const sourceOffer = await sourceOfferCursor.next() as ArangoDoc | undefined
|
||||||
|
if (!sourceOffer) throw new Error('No offer is available for the landing network story')
|
||||||
|
|
||||||
|
const focusCursor = await db.query(`
|
||||||
|
FOR hub IN nodes
|
||||||
|
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||||
|
FILTER hub.product_uuid == null
|
||||||
|
FILTER hub.latitude != null AND hub.longitude != null
|
||||||
|
LET dist = DISTANCE(hub.latitude, hub.longitude, @lat, @lon) / 1000
|
||||||
|
SORT dist ASC
|
||||||
|
LIMIT 1
|
||||||
|
RETURN hub
|
||||||
|
`, { lat: sourceOffer.latitude, lon: sourceOffer.longitude })
|
||||||
|
const focus = await focusCursor.next() as ArangoDoc | undefined
|
||||||
|
if (!focus) throw new Error('No hub is available for the landing network story')
|
||||||
|
|
||||||
|
const offersCursor = await db.query(`
|
||||||
|
FOR offer IN nodes
|
||||||
|
FILTER offer.node_type == 'offer'
|
||||||
|
FILTER offer.product_uuid != null AND offer.product_name != null
|
||||||
|
FILTER offer.supplier_uuid != null AND offer.supplier_name != null
|
||||||
FILTER offer.latitude != null AND offer.longitude != null
|
FILTER offer.latitude != null AND offer.longitude != null
|
||||||
LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
|
LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
|
||||||
COLLECT supplier_uuid = offer.supplier_uuid INTO offers
|
FILTER dist <= 500
|
||||||
LET first_offer = FIRST(offers).offer
|
SORT dist ASC
|
||||||
LET supplier_dist = DISTANCE(first_offer.latitude, first_offer.longitude, @lat, @lon) / 1000
|
LIMIT @offer_limit
|
||||||
LET supplier_node = FIRST(
|
RETURN MERGE(offer, {distance_km: dist})
|
||||||
FOR supplier IN nodes
|
`, { lat: focus.latitude, lon: focus.longitude, offer_limit: limit * 4 })
|
||||||
FILTER supplier._key == supplier_uuid
|
const offers = await offersCursor.all() as ArangoDoc[]
|
||||||
FILTER supplier.node_type == 'supplier'
|
const visibleOffers = offers.slice(0, limit)
|
||||||
RETURN supplier
|
|
||||||
)
|
|
||||||
SORT supplier_dist ASC
|
|
||||||
LIMIT @limit
|
|
||||||
RETURN {
|
|
||||||
_key: supplier_uuid,
|
|
||||||
name: supplier_node != null ? supplier_node.name : first_offer.supplier_name,
|
|
||||||
country: supplier_node != null ? supplier_node.country : first_offer.country,
|
|
||||||
latitude: supplier_node != null ? supplier_node.latitude : first_offer.latitude,
|
|
||||||
longitude: supplier_node != null ? supplier_node.longitude : first_offer.longitude,
|
|
||||||
distance_km: supplier_dist
|
|
||||||
}
|
|
||||||
`, { lat: focus.latitude, lon: focus.longitude, limit })
|
|
||||||
const neighbors = await neighborsCursor.all()
|
|
||||||
return {
|
return {
|
||||||
focus: mapLandingNetworkPoint(focus, 'HUB'),
|
focus: mapLandingNetworkPoint(focus, 'HUB'),
|
||||||
neighbors: neighbors.map((neighbor: ArangoDoc) => mapLandingNetworkPoint(neighbor, 'SUPPLIER')),
|
offers: visibleOffers.map(mapLandingNetworkOffer),
|
||||||
|
hubs: [],
|
||||||
|
products: landingProductsFromOffers(visibleOffers, limit),
|
||||||
|
suppliers: landingSuppliersFromOffers(visibleOffers, limit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,8 +666,8 @@ export const resolvers = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Query: {
|
Query: {
|
||||||
landingNetworkStory: async (_: unknown, args: { focusKind: LandingNetworkKind; neighborLimit?: number }) => {
|
landingNetworkStory: async (_: unknown, args: { focusKind: LandingNetworkKind; itemLimit?: number }) => {
|
||||||
return resolveLandingNetworkStory(args.focusKind, args.neighborLimit)
|
return resolveLandingNetworkStory(args.focusKind, args.itemLimit)
|
||||||
},
|
},
|
||||||
|
|
||||||
node: async (_: unknown, args: { uuid: string }) => {
|
node: async (_: unknown, args: { uuid: string }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user