Added local elastic vinmonopolet cache for query get by id.
First tries to search local elastic instance, if no results are found a reject is thrown and we search vinmonopolet api manually.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
const fetch = require("node-fetch");
|
const fetch = require("node-fetch");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const config = require(path.join(__dirname + "/../config/env/lottery.config"));
|
const config = require(path.join(__dirname + "/../config/env/lottery.config"));
|
||||||
|
const vinmonopoletCache = require(path.join(__dirname, "vinmonopoletCache"));
|
||||||
|
|
||||||
const convertToOurWineObject = wine => {
|
const convertToOurWineObject = wine => {
|
||||||
if (wine.basic.ageLimit === "18") {
|
if (wine.basic.ageLimit === "18") {
|
||||||
@@ -18,6 +19,20 @@ const convertToOurWineObject = wine => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const convertVinmonopoletProductResponseToWineObject = wine => {
|
||||||
|
return {
|
||||||
|
name: wine.name,
|
||||||
|
vivinoLink: "https://www.vinmonopolet.no" + wine.url,
|
||||||
|
rating: null,
|
||||||
|
occurences: 0,
|
||||||
|
id: wine.code,
|
||||||
|
year: wine.year,
|
||||||
|
image: wine.images[1].url,
|
||||||
|
price: wine.price.value,
|
||||||
|
country: wine.main_country.name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const convertToOurStoreObject = store => {
|
const convertToOurStoreObject = store => {
|
||||||
return {
|
return {
|
||||||
id: store.storeId,
|
id: store.storeId,
|
||||||
@@ -26,37 +41,32 @@ const convertToOurStoreObject = store => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchWinesByName = async (name, page = 1) => {
|
const searchWinesByName = (name, page = 1) => {
|
||||||
const pageSize = 15;
|
const pageSize = 25;
|
||||||
let url = new URL(
|
|
||||||
`https://apis.vinmonopolet.no/products/v0/details-normal?productShortNameContains=gato&maxResults=15`
|
|
||||||
);
|
|
||||||
url.searchParams.set("maxResults", pageSize);
|
|
||||||
url.searchParams.set("start", pageSize * (page - 1));
|
|
||||||
url.searchParams.set("productShortNameContains", name);
|
|
||||||
|
|
||||||
const vinmonopoletResponse = await fetch(url, {
|
return vinmonopoletCache.wineByQueryName(name, page, pageSize)
|
||||||
headers: {
|
.catch(_ => {
|
||||||
"Ocp-Apim-Subscription-Key": config.vinmonopoletToken
|
console.log(`No wines matching query: ${name} at page ${page} found in elastic index, searching vinmonopolet..`)
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(resp => resp.json())
|
|
||||||
.catch(err => console.error(err));
|
|
||||||
|
|
||||||
if (vinmonopoletResponse.errors != null) {
|
const url = `https://www.vinmonopolet.no/api/search?q=${name}:relevance:visibleInSearch:true&searchType=product&pageSize=${pageSize}¤tPage=${page-1}`
|
||||||
return vinmonopoletResponse.errors.map(error => {
|
const options = {
|
||||||
if (error.type == "UnknownProductError") {
|
headers: { "Content-Type": 'application/json' }
|
||||||
return res.status(404).json({
|
};
|
||||||
message: error.message
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const winesConverted = vinmonopoletResponse.map(convertToOurWineObject).filter(Boolean);
|
|
||||||
|
|
||||||
return winesConverted;
|
return fetch(url, options)
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.ok == false) {
|
||||||
|
return Promise.reject({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `No wines matching query ${name} at page ${page} found in local cache or at vinmonopolet.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.json()
|
||||||
|
.then(response => response?.productSearchResult?.products)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(wines => wines.map(convertVinmonopoletProductResponseToWineObject))
|
||||||
};
|
};
|
||||||
|
|
||||||
const wineByEAN = ean => {
|
const wineByEAN = ean => {
|
||||||
@@ -67,16 +77,30 @@ const wineByEAN = ean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wineById = id => {
|
const wineById = id => {
|
||||||
const url = `https://apis.vinmonopolet.no/products/v0/details-normal?productId=${id}`;
|
return vinmonopoletCache.wineById(id)
|
||||||
const options = {
|
.catch(_ => {
|
||||||
headers: {
|
console.log(`Wine id: ${id} not found in elastic index, searching vinmonopolet..`)
|
||||||
"Ocp-Apim-Subscription-Key": config.vinmonopoletToken
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(url, options)
|
const url = `https://www.vinmonopolet.no/api/products/${id}?fields=FULL`
|
||||||
.then(resp => resp.json())
|
const options = {
|
||||||
.then(response => response.map(convertToOurWineObject));
|
headers: {
|
||||||
|
"Content-Type": 'application/json'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch(url, options)
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.ok == false) {
|
||||||
|
return Promise.reject({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `Wine with id ${id} not found in local cache or at vinmonopolet.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.json()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(wine => convertVinmonopoletProductResponseToWineObject(wine))
|
||||||
};
|
};
|
||||||
|
|
||||||
const allStores = () => {
|
const allStores = () => {
|
||||||
|
|||||||
98
api/vinmonopoletCache.js
Normal file
98
api/vinmonopoletCache.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
const fetch = require("node-fetch");
|
||||||
|
|
||||||
|
const ELASTIC_URL = 'http://localhost:9200';
|
||||||
|
const INDEX_URL = `${ELASTIC_URL}/wines*`;
|
||||||
|
|
||||||
|
const verifyAndUnpackElasticSearchResult = response => {
|
||||||
|
const searchHits = response?.hits?.hits;
|
||||||
|
|
||||||
|
if (searchHits == null || searchHits.length == 0) {
|
||||||
|
return Promise.reject({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `Nothing found in vinmonopolet cache matching this.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchHits;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWineObjectFromSearchHit = hit => {
|
||||||
|
const { wine } = hit?._source;
|
||||||
|
|
||||||
|
if (wine == null) {
|
||||||
|
return Promise.reject({
|
||||||
|
statusCode: 500,
|
||||||
|
message: `Found response, but it's missing a wine object. Unable to convert!`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return wine;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wineById = id => {
|
||||||
|
const url = `${INDEX_URL}/_search`
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
"size": 1,
|
||||||
|
"query": {
|
||||||
|
"match": {
|
||||||
|
"wine.code": id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_source": {
|
||||||
|
"includes": "wine"
|
||||||
|
},
|
||||||
|
"sort": [
|
||||||
|
{
|
||||||
|
"@timestamp": "desc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(url, options)
|
||||||
|
.then(resp => resp.json())
|
||||||
|
.then(verifyAndUnpackElasticSearchResult)
|
||||||
|
.then(searchHits => getWineObjectFromSearchHit(searchHits[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
const wineByQueryName = (name, page=1, size=25) => {
|
||||||
|
const url = `${INDEX_URL}/_search`
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', },
|
||||||
|
body: JSON.stringify({
|
||||||
|
"from": page - 1,
|
||||||
|
"size": size,
|
||||||
|
"query": {
|
||||||
|
"multi_match" : {
|
||||||
|
"query" : name,
|
||||||
|
"fields": ["wine.name"],
|
||||||
|
"fuzziness": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sort": [
|
||||||
|
{
|
||||||
|
"_score": {
|
||||||
|
"order": "desc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_source": {
|
||||||
|
"includes": "wine"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch(url, options)
|
||||||
|
.then(resp => resp.json())
|
||||||
|
.then(verifyAndUnpackElasticSearchResult)
|
||||||
|
.then(searchHits => Promise.all(searchHits.map(getWineObjectFromSearchHit)))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
wineById,
|
||||||
|
wineByQueryName
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user