From 1b1cdb59f6d9cb2a342bb647e03efdd60c7ee8de Mon Sep 17 00:00:00 2001 From: Patrick <50352812+Mueller-Patrick@users.noreply.github.com> Date: Mon, 5 Apr 2021 16:34:11 +0200 Subject: [PATCH] BETTERZON-48: Adding API functionality to get best available deals (#20) --- Backend/src/models/prices/price.interface.ts | 3 + Backend/src/models/prices/prices.router.ts | 23 ++++- Backend/src/models/prices/prices.service.ts | 94 ++++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/Backend/src/models/prices/price.interface.ts b/Backend/src/models/prices/price.interface.ts index 49030e8..956c9d5 100644 --- a/Backend/src/models/prices/price.interface.ts +++ b/Backend/src/models/prices/price.interface.ts @@ -4,4 +4,7 @@ export interface Price { vendor_id: number; price_in_cents: number; timestamp: Date; + // Only for deals + amazonDifference?: number; + amazonDifferencePercent?: number; } diff --git a/Backend/src/models/prices/prices.router.ts b/Backend/src/models/prices/prices.router.ts index 9672efa..889f817 100644 --- a/Backend/src/models/prices/prices.router.ts +++ b/Backend/src/models/prices/prices.router.ts @@ -19,7 +19,7 @@ export const pricesRouter = express.Router(); * Controller Definitions */ -// GET items/ +// GET prices/ pricesRouter.get('/', async (req: Request, res: Response) => { try { @@ -44,7 +44,7 @@ pricesRouter.get('/', async (req: Request, res: Response) => { } }); -// GET items/:id +// GET prices/:id pricesRouter.get('/:id', async (req: Request, res: Response) => { const id: number = parseInt(req.params.id, 10); @@ -63,6 +63,25 @@ pricesRouter.get('/:id', async (req: Request, res: Response) => { } }); +// GET prices/bestDeals + +pricesRouter.get('/bestDeals/:amount', async (req: Request, res: Response) => { + const amount: number = parseInt(req.params.amount, 10); + + if (!amount) { + res.status(400).send('Missing parameters.'); + return; + } + + try { + const prices: Prices = await PriceService.getBestDeals(amount); + + res.status(200).send(prices); + } catch (e) { + res.status(404).send(e.message); + } +}); + // POST items/ // pricesRouter.post('/', async (req: Request, res: Response) => { diff --git a/Backend/src/models/prices/prices.service.ts b/Backend/src/models/prices/prices.service.ts index b0a8038..b639db8 100644 --- a/Backend/src/models/prices/prices.service.ts +++ b/Backend/src/models/prices/prices.service.ts @@ -186,6 +186,100 @@ export const findByVendor = async (product: string, vendor: string, type: string return priceRows; }; +export const getBestDeals = async (amount: number): Promise => { + let conn; + let priceRows = []; + try { + conn = await pool.getConnection(); + + let allPrices: Record = {}; + + // Get newest prices for every product at every vendor + + const rows = await conn.query( + 'WITH summary AS (\n' + + ' SELECT p.product_id,\n' + + ' p.vendor_id,\n' + + ' p.price_in_cents,\n' + + ' p.timestamp,\n' + + ' ROW_NUMBER() OVER(\n' + + ' PARTITION BY p.product_id, p.vendor_id\n' + + ' ORDER BY p.timestamp DESC) AS rk\n' + + ' FROM prices p)\n' + + 'SELECT s.*\n' + + 'FROM summary s\n' + + 'WHERE s.rk = 1'); + + // Write returned values to allPrices map with product id as key and a list of prices as value + for (let row in rows) { + if (row !== 'meta') { + if (!allPrices[parseInt(rows[row].product_id)]) { + allPrices[parseInt(rows[row].product_id)] = []; + } + + allPrices[parseInt(rows[row].product_id)].push(rows[row]); + } + } + + // Iterate over all prices to find the products with the biggest difference between amazon and other vendor + let deals = []; + for (let productId in Object.keys(allPrices)) { + if (allPrices[productId]) { + let pricesForProd = allPrices[productId]; + + // Get amazon price and lowest price from other vendor + let amazonPrice = {} as Price; + let lowestPrice = {} as Price; + pricesForProd.forEach(function(price, priceIndex) { + if (price.vendor_id === 1) { + amazonPrice = price; + } else { + if (!lowestPrice.price_in_cents || lowestPrice.price_in_cents > price.price_in_cents) { + lowestPrice = price; + } + } + }); + + // Create deal object and add it to list + let deal = { + 'product_id': lowestPrice.product_id, + 'vendor_id': lowestPrice.vendor_id, + 'price_in_cents': lowestPrice.price_in_cents, + 'timestamp' :lowestPrice.timestamp, + 'amazonDifference': (amazonPrice.price_in_cents - lowestPrice.price_in_cents), + 'amazonDifferencePercent': ((1 - (lowestPrice.price_in_cents / amazonPrice.price_in_cents)) * 100), + }; + + // Push only deals were the amazon price is actually higher + if(deal.amazonDifferencePercent > 0) { + deals.push(deal); + } + } + } + + // Sort to have the best deals on the top + deals.sort((a, b) => a.amazonDifferencePercent < b.amazonDifferencePercent ? 1 : -1); + + // Return only as many records as requested or the maximum amount of found deals, whatever is less + let maxAmt = Math.min(amount, deals.length); + + for (let dealIndex = 0; dealIndex < maxAmt; dealIndex++){ + //console.log(deals[dealIndex]); + priceRows.push(deals[dealIndex] as Price); + } + + } catch (err) { + console.log(err); + throw err; + } finally { + if (conn) { + conn.end(); + } + } + + return priceRows; +}; + // export const create = async (newItem: Product): Promise => { // let conn; // try {