diff --git a/.gitignore b/.gitignore
index 103250b..ff35e3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ speed-measure-plugin*.json
!Frontend.iml
!Backend.iml
!CucumberTests.iml
+!Crawler.iml
# Include IntelliJ modules
!/.idea/modules.xml
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 66aebf2..44a6847 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -4,6 +4,7 @@
+
diff --git a/Backend/src/models/categories/categories.router.ts b/Backend/src/models/categories/categories.router.ts
index 409ca71..1af2db5 100644
--- a/Backend/src/models/categories/categories.router.ts
+++ b/Backend/src/models/categories/categories.router.ts
@@ -19,7 +19,7 @@ export const categoriesRouter = express.Router();
* Controller Definitions
*/
-// GET items/
+// GET categories/
categoriesRouter.get('/', async (req: Request, res: Response) => {
try {
@@ -31,7 +31,7 @@ categoriesRouter.get('/', async (req: Request, res: Response) => {
}
});
-// GET items/:id
+// GET categories/:id
categoriesRouter.get('/:id', async (req: Request, res: Response) => {
const id: number = parseInt(req.params.id, 10);
@@ -50,7 +50,7 @@ categoriesRouter.get('/:id', async (req: Request, res: Response) => {
}
});
-// GET items/:name
+// GET categories/search/:term
categoriesRouter.get('/search/:term', async (req: Request, res: Response) => {
const term: string = req.params.term;
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 {
diff --git a/Backend/src/models/products/products.router.ts b/Backend/src/models/products/products.router.ts
index 0c7cc5c..f2f3353 100644
--- a/Backend/src/models/products/products.router.ts
+++ b/Backend/src/models/products/products.router.ts
@@ -19,7 +19,7 @@ export const productsRouter = express.Router();
* Controller Definitions
*/
-// GET items/
+// GET products/
productsRouter.get('/', async (req: Request, res: Response) => {
try {
@@ -31,7 +31,7 @@ productsRouter.get('/', async (req: Request, res: Response) => {
}
});
-// GET items/:id
+// GET products/:id
productsRouter.get('/:id', async (req: Request, res: Response) => {
const id: number = parseInt(req.params.id, 10);
@@ -50,7 +50,7 @@ productsRouter.get('/:id', async (req: Request, res: Response) => {
}
});
-// GET items/:name
+// GET products/search/:term
productsRouter.get('/search/:term', async (req: Request, res: Response) => {
const term: string = req.params.term;
@@ -69,6 +69,8 @@ productsRouter.get('/search/:term', async (req: Request, res: Response) => {
}
});
+// GET products/bestDeals
+
// POST items/
diff --git a/Crawler/Crawler.iml b/Crawler/Crawler.iml
new file mode 100644
index 0000000..80cc739
--- /dev/null
+++ b/Crawler/Crawler.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Crawler/requirements.txt b/Crawler/requirements.txt
new file mode 100644
index 0000000..d4a7eda
--- /dev/null
+++ b/Crawler/requirements.txt
@@ -0,0 +1 @@
+pymysql
diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts
index cb7e608..918cb54 100644
--- a/Frontend/src/app/app.module.ts
+++ b/Frontend/src/app/app.module.ts
@@ -17,6 +17,8 @@ import {FormsModule} from '@angular/forms';
import {PageNotFoundPageComponent} from './pages/page-not-found-page/page-not-found-page.component';
import {MatMenuModule} from '@angular/material/menu';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { ImprintComponent } from './pages/imprint/imprint.component';
+import { PrivacyComponent } from './pages/privacy/privacy.component';
@NgModule({
declarations: [
@@ -29,7 +31,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
ProductSearchPageComponent,
HeaderComponent,
NewestPricesListComponent,
- PageNotFoundPageComponent
+ PageNotFoundPageComponent,
+ ImprintComponent,
+ PrivacyComponent
],
imports: [
BrowserModule,
diff --git a/Frontend/src/app/app.routing.ts b/Frontend/src/app/app.routing.ts
index efb2a36..c38d9ba 100644
--- a/Frontend/src/app/app.routing.ts
+++ b/Frontend/src/app/app.routing.ts
@@ -7,11 +7,15 @@ import {LandingpageComponent} from './pages/landingpage/landingpage.component';
import {ProductDetailPageComponent} from './pages/product-detail-page/product-detail-page.component';
import {ProductSearchPageComponent} from './pages/product-search-page/product-search-page.component';
import {PageNotFoundPageComponent} from './pages/page-not-found-page/page-not-found-page.component';
+import {ImprintComponent} from './pages/imprint/imprint.component';
+import {PrivacyComponent} from './pages/privacy/privacy.component';
const routes: Routes = [
{path: '', component: LandingpageComponent},
{path: 'search', component: ProductSearchPageComponent},
{path: 'product/:id', component: ProductDetailPageComponent},
+ {path: 'impressum', component: ImprintComponent},
+ {path: 'datenschutz', component: PrivacyComponent},
{path: '**', component: PageNotFoundPageComponent}
];
diff --git a/Frontend/src/app/components/footer/footer.component.css b/Frontend/src/app/components/footer/footer.component.css
index c39569a..0d7949b 100644
--- a/Frontend/src/app/components/footer/footer.component.css
+++ b/Frontend/src/app/components/footer/footer.component.css
@@ -5,7 +5,6 @@
width: 100%;
background-color: dimgrey;
color: white;
- text-align: center;
}
.icon-3d {
@@ -13,5 +12,23 @@
color: #fff;
}
+#footer-icons {
+ text-align: center;
+}
+
+#imprintSection {
+ position: fixed;
+ right: 1em;
+ bottom: 1em;
+ width: 100%;
+ text-align: right;
+ padding-right: 1em;
+}
+
+#imprintSection a {
+ color: white;
+ text-decoration: none;
+}
+
diff --git a/Frontend/src/app/components/footer/footer.component.html b/Frontend/src/app/components/footer/footer.component.html
index e6d17d8..a57e963 100644
--- a/Frontend/src/app/components/footer/footer.component.html
+++ b/Frontend/src/app/components/footer/footer.component.html
@@ -3,12 +3,16 @@