Merge remote-tracking branch 'origin/develop' into BETTERZON-140

This commit is contained in:
Jegor 2021-06-15 10:24:02 +02:00
commit 62795fd3f8
12 changed files with 361 additions and 19 deletions

View File

@ -89,9 +89,9 @@ contactpersonsRouter.post('/', async (req: Request, res: Response) => {
const success = await ContactPersonService.createContactEntry(user.user_id, vendor_id, first_name, last_name, gender, email, phone); const success = await ContactPersonService.createContactEntry(user.user_id, vendor_id, first_name, last_name, gender, email, phone);
if (success) { if (success) {
res.sendStatus(201); res.status(201).send({});
} else { } else {
res.sendStatus(500); res.status(500).send({});
} }
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);
@ -118,9 +118,9 @@ contactpersonsRouter.put('/:id', async (req: Request, res: Response) => {
const success = await ContactPersonService.updateContactEntry(user.user_id, contact_person_id, vendor_id, first_name, last_name, gender, email, phone); const success = await ContactPersonService.updateContactEntry(user.user_id, contact_person_id, vendor_id, first_name, last_name, gender, email, phone);
if (success) { if (success) {
res.sendStatus(200); res.status(200).send({});
} else { } else {
res.sendStatus(500); res.status(500).send({});
} }
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);

View File

@ -28,7 +28,7 @@ crawlingstatusRouter.get('/', async (req: Request, res: Response) => {
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip);
if (!user.is_admin) { if (!user.is_admin) {
res.sendStatus(403); res.status(403).send({});
return; return;
} }

View File

@ -117,9 +117,9 @@ pricesRouter.post('/', async (req: Request, res: Response) => {
const success = await PriceService.createPriceEntry(user.user_id, vendor_id, product_id, price_in_cents); const success = await PriceService.createPriceEntry(user.user_id, vendor_id, product_id, price_in_cents);
if (success) { if (success) {
res.sendStatus(201); res.status(201).send({});
} else { } else {
res.sendStatus(500); res.status(500).send({});
} }
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);

View File

@ -120,7 +120,7 @@ productsRouter.post('/', async (req: Request, res: Response) => {
const result: boolean = await ProductService.addNewProduct(asin); const result: boolean = await ProductService.addNewProduct(asin);
if (result) { if (result) {
res.sendStatus(201); res.status(201).send({});
} else { } else {
res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'})); res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'}));
} }

View File

@ -50,7 +50,7 @@ usersRouter.post('/register', async (req: Request, res: Response) => {
res.cookie('betterauth', JSON.stringify({ res.cookie('betterauth', JSON.stringify({
id: session.session_id, id: session.session_id,
key: session.session_key key: session.session_key
}), {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)}).sendStatus(201); }), {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)}).status(201).send({});
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);
res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'})); res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'}));
@ -83,7 +83,7 @@ usersRouter.post('/login', async (req: Request, res: Response) => {
res.cookie('betterauth', JSON.stringify({ res.cookie('betterauth', JSON.stringify({
id: session.session_id, id: session.session_id,
key: session.session_key key: session.session_key
}), {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)}).sendStatus(200); }), {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)}).status(200).send({});
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);
res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'})); res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'}));

View File

@ -100,9 +100,9 @@ vendorsRouter.put('/manage/deactivatelisting', async (req: Request, res: Respons
const success = await VendorService.deactivateListing(user.user_id, vendor_id, product_id); const success = await VendorService.deactivateListing(user.user_id, vendor_id, product_id);
if (success) { if (success) {
res.sendStatus(200); res.status(200).send({});
} else { } else {
res.sendStatus(500); res.status(500).send({});
} }
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);
@ -123,9 +123,9 @@ vendorsRouter.put('/manage/shop/deactivate/:id', async (req: Request, res: Respo
const success = await VendorService.setShopStatus(user.user_id, vendor_id, false); const success = await VendorService.setShopStatus(user.user_id, vendor_id, false);
if (success) { if (success) {
res.sendStatus(200); res.status(200).send({});
} else { } else {
res.sendStatus(500); res.status(500).send({});
} }
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);
@ -146,9 +146,9 @@ vendorsRouter.put('/manage/shop/activate/:id', async (req: Request, res: Respons
const success = await VendorService.setShopStatus(user.user_id, vendor_id, true); const success = await VendorService.setShopStatus(user.user_id, vendor_id, true);
if (success) { if (success) {
res.sendStatus(200); res.status(200).send({});
} else { } else {
res.sendStatus(500); res.status(500).send({});
} }
} catch (e) { } catch (e) {
console.log('Error handling a request: ' + e.message); console.log('Error handling a request: ' + e.message);

View File

@ -92,7 +92,8 @@
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"codeCoverage": true, "codeCoverage": true,
"codeCoverageExclude": [ "codeCoverageExclude": [
"src/app/mocks/mock.service.ts" "src/app/mocks/mock.service.ts",
"src/app/services/api.service.ts"
], ],
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",

View File

@ -47,7 +47,9 @@ export class ProductDetailsComponent implements OnInit {
} }
getProduct(): void { getProduct(): void {
this.apiService.getProduct(this.productId).subscribe(product => {this.product = product}); this.apiService.getProduct(this.productId).subscribe(product => {
this.product = product;
});
} }
getPrices(): void { getPrices(): void {

View File

@ -5,3 +5,24 @@ export interface Price {
price_in_cents: number; price_in_cents: number;
timestamp: Date; timestamp: Date;
} }
export class Deal implements Price {
price_id: number;
product_id: number;
vendor_id: number;
price_in_cents: number;
timestamp: Date;
amazonDifference: number;
amazonDifferencePercent: number;
constructor(price_id: number, product_id: number, vendor_id: number, price_in_cents: number, timestamp: Date, amazonDifference: number,
amazonDifferencePercent: number) {
this.price_id = price_id;
this.product_id = product_id;
this.vendor_id = vendor_id;
this.price_in_cents = price_in_cents;
this.timestamp = timestamp;
this.amazonDifference = amazonDifference;
this.amazonDifferencePercent = amazonDifferencePercent;
}
}

View File

@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import process from 'process'; import process from 'process';
import {Product} from '../models/product'; import {Product} from '../models/product';
import {Price} from '../models/price'; import {Deal, Price} from '../models/price';
import {Observable, of} from 'rxjs'; import {Observable, of} from 'rxjs';
import {Vendor} from '../models/vendor'; import {Vendor} from '../models/vendor';
import {PriceAlarm} from '../models/pricealarm'; import {PriceAlarm} from '../models/pricealarm';
@ -71,6 +71,60 @@ export class ApiService {
} }
} }
/**
* Gets a list of all specified products
* @param ids The ids of the products to get
* @return Observable<Product[]> An observable list of products
*/
getProductsByIds(ids: number[]): Observable<Product[]> {
try {
return this.http.get<Product[]>((this.apiUrl + '/products/list/[' + ids.toString() + ']'));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets a list of all products that are available at the specified vendor
* @param vendor The vendor to get the products for
* @return Observable<Product[]> An observable list of products
*/
getProductsByVendor(vendor: number): Observable<Product[]> {
try {
return this.http.get<Product[]>((this.apiUrl + '/products/vendor/' + vendor));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Creates a new product entry
* @param asinOrLink The amazon link or asin of the product
* @return Observable<any> The observable response of the api
*/
addNewProduct(asinOrLink: string): Observable<any> {
let asin = '';
// Check if the parameter is a link or an asin
const linkRegex: RegExp = /^http[s]{0,1}:\/\/.*\/dp\/(.[^\/]*)\/{0,1}.*$/;
const matches = linkRegex.exec(asinOrLink);
if (matches) {
// param is a link, extract asin
asin = matches[1] ?? '';
} else {
// param is not a link, suspect it is an asin
asin = asinOrLink;
}
try {
return this.http.post((this.apiUrl + '/products'), JSON.stringify({
asin
}));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/* ____ _ /* ____ _
/ __ \_____(_)_______ _____ / __ \_____(_)_______ _____
@ -79,6 +133,19 @@ export class ApiService {
/_/ /_/ /_/\___/\___/____/ /_/ /_/ /_/\___/\___/____/
*/ */
/**
* Gets the specified price from the API
* @param id The id of the price to get
* @return Observable<Price> An observable containing a single price
*/
getPrice(id: number): Observable<Price> {
try {
return this.http.get<Price>((this.apiUrl + '/prices/' + id));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/** /**
* Gets a list of all prices * Gets a list of all prices
* @return Observable<Price[]> An observable list of prices * @return Observable<Price[]> An observable list of prices
@ -140,6 +207,51 @@ export class ApiService {
} }
} }
/**
* Gets the currently best deals
* @param amount The amount of deals to get
* @return Observable<Deal[]> An observable list of deals
*/
getBestDeals(amount: number): Observable<Deal[]> {
try {
return this.http.get<Deal[]>((this.apiUrl + '/prices/bestDeals/' + amount));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets a list of all prices for the specified product
* @param product The product to get prices for
* @return Observable<Price[]> An observable list of prices
*/
getPricesByProduct(products: number[]): Observable<Price[]> {
try {
console.log('IDs: ' + products.toString());
return this.http.get<Price[]>((this.apiUrl + '/prices/byProduct/list/[' + products.toString() + ']'));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Creates a new price entry
* @param vendorId The vendor to add the price for
* @param productId The product to add the price to
* @param price The price in cents to add
* @return Observable<any> The observable response of the api
*/
addNewPrice(vendorId: number, productId: number, price: number): Observable<any> {
try {
return this.http.post((this.apiUrl + '/prices'), JSON.stringify({
vendor_id: vendorId,
product_id: productId,
price_in_cents: price
}));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/* _ __ __ /* _ __ __
| | / /__ ____ ____/ /___ __________ | | / /__ ____ ____/ /___ __________

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Patrick Müller, Georg Reichert, Henning Sextro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,185 @@
CREATE DATABASE `Betterzon`;
USE `Betterzon`;
create table categories
(
category_id int auto_increment
primary key,
name text null
);
create table crawling_processes
(
process_id int auto_increment
primary key,
started_timestamp datetime default current_timestamp() null,
combinations_to_crawl int null
);
create table manufacturers
(
manufacturer_id int auto_increment
primary key,
name text null
);
create table products
(
product_id int auto_increment
primary key,
asin text null,
is_active tinyint null,
name text null,
short_description text null,
long_description text null,
image_guid text null,
date_added date null,
last_modified datetime null,
manufacturer_id int null,
selling_rank text null,
category_id int null,
constraint FK_products_categories
foreign key (category_id) references categories (category_id),
constraint FK_products_manufacturers
foreign key (manufacturer_id) references manufacturers (manufacturer_id)
);
create table users
(
user_id int auto_increment
primary key,
username text not null,
email text null,
bcrypt_password_hash text null,
registration_date datetime default current_timestamp() null,
last_login_date datetime default current_timestamp() null,
is_admin tinyint(1) default 0 null,
constraint users_username_uindex
unique (username) using hash
);
create table price_alarms
(
alarm_id int auto_increment
primary key,
user_id int not null,
product_id int not null,
defined_price int null,
constraint price_alarms_products_product_id_fk
foreign key (product_id) references products (product_id)
on update cascade on delete cascade,
constraint price_alarms_users_user_id_fk
foreign key (user_id) references users (user_id)
on update cascade on delete cascade
);
create table sessions
(
session_id int auto_increment
primary key,
user_id int not null,
session_key_hash text null,
createdDate datetime default current_timestamp() null,
lastLogin datetime null,
validUntil datetime null,
validDays int null,
last_IP text null,
constraint sessions_users_user_id_fk
foreign key (user_id) references users (user_id)
on update cascade on delete cascade
);
create table vendors
(
vendor_id int auto_increment
primary key,
admin_id int null,
name text null,
streetname text null,
zip_code int null,
city text null,
country_code text null,
phone text null,
website text null,
isActive tinyint(1) default 1 not null,
constraint vendors_users_user_id_fk
foreign key (admin_id) references users (user_id)
on update set null on delete set null
);
create table contact_persons
(
contact_person_id int auto_increment
primary key,
first_name text default '0' not null,
last_name text default '0' not null,
gender text default '0' not null,
email text default '0' not null,
phone text default '0' not null,
vendor_id int default 0 not null,
constraint FK_contact_persons_vendors
foreign key (vendor_id) references vendors (vendor_id)
);
create table crawling_status
(
status_id int auto_increment
primary key,
process_id int not null,
instance_url text null,
product_id int not null,
vendor_id int not null,
success tinyint(1) not null,
constraint crawling_status_crawling_processes_process_id_fk
foreign key (process_id) references crawling_processes (process_id)
on update cascade on delete cascade,
constraint crawling_status_products_product_id_fk
foreign key (product_id) references products (product_id)
on update cascade on delete cascade,
constraint crawling_status_vendors_vendor_id_fk
foreign key (vendor_id) references vendors (vendor_id)
on update cascade on delete cascade
);
create table favorite_shops
(
favorite_id int auto_increment
primary key,
vendor_id int not null,
user_id int not null,
constraint favorite_shops_users_user_id_fk
foreign key (user_id) references users (user_id)
on update cascade on delete cascade,
constraint favorite_shops_vendors_vendor_id_fk
foreign key (vendor_id) references vendors (vendor_id)
on update cascade on delete cascade
);
create table prices
(
price_id int auto_increment
primary key,
product_id int default 0 null,
vendor_id int null,
price_in_cents int null,
timestamp datetime default current_timestamp() null,
active_listing tinyint(1) default 1 not null,
constraint FK_prices_products
foreign key (product_id) references products (product_id),
constraint FK_prices_vendors
foreign key (vendor_id) references vendors (vendor_id)
);
create table product_links
(
product_link_id int auto_increment
primary key,
product_id int default 0 not null,
vendor_id int default 0 not null,
url text default '0' not null,
constraint FK__products
foreign key (product_id) references products (product_id),
constraint FK__vendors
foreign key (vendor_id) references vendors (vendor_id)
);