Compare commits

...

14 Commits

Author SHA1 Message Date
Patrick
3eea4f7f72
Merge branch 'develop' into BETTERZON-120 2021-06-07 16:47:35 +02:00
1fa69c334b Merging develop 2021-06-06 22:53:24 +02:00
Patrick
e760247866
Merge pull request #76 from Mueller-Patrick/BETTERZON-139
BETTERZON-139: Fixing Codacy Error Prone Issues
2021-06-06 22:41:31 +02:00
f3f1cba9ea BETTERZON-139: Fixing Codacy Error Prone Issues 2021-06-06 22:38:28 +02:00
henningxtro
3be39fad76
Api tests (#75)
* Added API Tests for Postman - BETTERZON-127 BETTERZON-128 BETTERZON-129 BETTERZON-130 BETTERZON-131 BETTERZON-132 BETTERZON-133 BETTERZON-134 BETTERZON-135

* Updated Postman Tests

Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>
2021-05-30 16:32:15 +02:00
Patrick
5d3e48a3c8
Update README.md 2021-05-30 16:25:33 +02:00
5a4ea9a134 Merge remote-tracking branch 'origin/master' into develop 2021-05-30 12:11:47 +02:00
Patrick
d362c68fac
BETTERZON-126: Correcting HTTP status codes sent by API (#72) 2021-05-30 12:07:08 +02:00
Patrick
25b6b43e9f
BETTERZON-124: Adding service function for crawling status API (#71) 2021-05-29 13:57:19 +02:00
Patrick
63362fbe67
BETTERZON-125: Adding service functions for manufacturer API (#70) 2021-05-29 13:51:07 +02:00
Patrick
6bb1c8f66b
BETTERZON-123: Adding service functions for categories API (#69) 2021-05-29 13:43:50 +02:00
Patrick
7dc76649fc
BETTERZON-121: Adding service functions for contact persons API (#68) 2021-05-29 13:28:54 +02:00
Patrick
6e8c52857f
Master deployment (#67)
* BETTERZON-58: Basic Functionality with scrapy (#33)

* BETTERZON-73: Adding API endpoint that returns the lowest non-amazon prices for a given list of product ids (#32)

* BETTERZON-75: User registration API endpoint (#34)

* BETTERZON-75: Adding backend functions to enable user registration

* BETTERZON-75: Adding regex to check email and username

* BETTERZON-83: FE unit testing (#35)

* BETTERZON-83: Making pre-generated unit tests work

* BETTERZON-83: Writing unit tests for angular to improve code coverage

* BETTERZON-79: Adding API endpoint for logging in (#36)

* BETTERZON-84: Adding service method to check if a session is valid (#37)

* BETTERZON-77: Changing error behavior as the previous behavior cloud have opened up security vulnerabilities (#38)

* BETTERZON-76: Adding method descriptions for backend service methods (#40)

* Adding Codacy code quality badge to README

* BETTERZON-89: Refactoring / Reformatting and adding unit tests (#41)

* BETTERZON-90: Adding API endpoint for creating price alarms (#42)

* BETTERZON-91: Adding API endpoint to GET all price alarms for the currently logged in user (#43)

* BETTERZON-92: Adding API endpoint to edit (update) price alarms (#44)

* BETTERZON-99: Adding some basic cucumber tests (#45)

* BETTERZON-100: Switching to cookies for session management (#46)

* BETTERZON-100: Switching session handling to cookies

* BETTERZON-100: Some code reformatting

* BETTERZON-100: Some more code reformatting

* BETTERZON-93: Adding API endpoint to get managed shops (#47)

* BETTERZON-94: Adding API endpoint to deactivate price listings as a vendor manager (#48)

* BETTERZON-97: Adding API endpoint to get all products listed by a specific vendor (#50)

* BETTERZON-98: Adding API endpoint for adding price entries as a registered vendor manager (#51)

* BETTERZON-95: Adding API endpoint for getting, inserting and updating contact persons (#52)

* BETTERZON-58 (#53)

* BETTERZON-58: Basic Functionality with scrapy

* Added independent crawler function, yielding price

* moved logic to amazon.py

* .

* moved scrapy files to unused folder

* Added basic amazon crawler using beautifulsoup4

* Connected Api to Crawler

* Fixed string concatenation for sql statement in getProductLinksForProduct

* BETTERZON-58: Fixing SQL insert

* BETTERZON-58: Adding access key verification

* BETTERZON-58: Fixing API endpoint of the crawler
- The list of products in the API request was treated like a string and henceforth, only the first product has been crawled

* Added another selector for price on amazon (does not work for books)

Co-authored-by: root <root@DESKTOP-ARBPL82.localdomain>
Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>

* BETTERZON-96: Adding API endpoint for delisting a whole vendor (#54)

* BETTERZON-101: Adding service functions for pricealarms api (#55)

- Not properly tested though as login functionality is required to test but not yet implemented

* BETTERZON-110: Refactoring, reformatting and commenting api service (#56)

* BETTERZON-107: Refactoring code with Proxy as design pattern (#49)

* BETTERZON-78 (#39)

* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>

* BETTERZON-109 (#57)

* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

* wip: new component created and added to the app.module.ts. Added a minimal grid layout.

* wip: all components were wrapped now. Grid structure has been applied to the main wrapper-class "container".

* wip: component created and added to the app.module.ts

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>

* BETTERZON-108 (#58)

* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

* wip: new component created and added to the app.module.ts. Added a minimal grid layout.

* wip: all components were wrapped now. Grid structure has been applied to the main wrapper-class "container".

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>

* BETTERZON-106 (#59)

* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

* wip: new component created and added to the app.module.ts. Added a minimal grid layout.

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>

* BETTEZON-102 (#60)

* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>

* BETTERZON-113, BETTERZON-114, BETTERZON-115: Adding API endpoint for favorite shops (#61)

* BETTERZON-116: Adding API endpoint for searching a new product (#62)

* BETTERZON-117: Adding API endpoint for getting the latest crawling status (#63)

* BETTERZON-111: Adding service functions for login and registration (#64)

* BETTERZON-112: Adding service functions for managing vendor shops (#65)

* BETTERZON-118: Adding service functions for managing favorite shops (#66)

Co-authored-by: henningxtro <sextro.henning@student.dhbw-karlsruhe.de>
Co-authored-by: root <root@DESKTOP-ARBPL82.localdomain>
Co-authored-by: Reboooooorn <61185041+Reboooooorn@users.noreply.github.com>
2021-05-29 10:58:27 +02:00
Patrick
197c39a61d
Merge pull request #31 from Mueller-Patrick/develop
Master Deployment for API changes
2021-04-17 12:34:10 +02:00
12 changed files with 2168 additions and 7 deletions

View File

@ -89,7 +89,7 @@ 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(200); res.sendStatus(201);
} else { } else {
res.sendStatus(500); res.sendStatus(500);
} }

View File

@ -85,11 +85,11 @@ pricealarmsRouter.put('/', async (req: Request, res: Response) => {
return; return;
} }
// Create price alarm // Update price alarm
const success = await PriceAlarmsService.updatePriceAlarm(alarm_id, user.user_id, defined_price); const success = await PriceAlarmsService.updatePriceAlarm(alarm_id, user.user_id, defined_price);
if (success) { if (success) {
res.status(201).send(JSON.stringify({success: true})); res.status(200).send(JSON.stringify({success: true}));
return; return;
} else { } else {
res.status(500).send(JSON.stringify({success: false})); res.status(500).send(JSON.stringify({success: false}));

View File

@ -117,7 +117,7 @@ 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(200); res.sendStatus(201);
} else { } else {
res.sendStatus(500); res.sendStatus(500);
} }

View File

@ -105,7 +105,7 @@ usersRouter.post('/checkSessionValid', async (req: Request, res: Response) => {
} }
// Send the session details back to the user // Send the session details back to the user
res.status(201).send(user); res.status(200).send(user);
} 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

@ -1,10 +1,10 @@
# Base image # Base image
FROM python FROM python:3.9.5-buster
# Create directories and copy files # Create directories and copy files
RUN echo 'Creating directory and copying files' RUN echo 'Creating directory and copying files'
RUN mkdir /crawler RUN mkdir /crawler
ADD . /crawler COPY . /crawler
WORKDIR /crawler WORKDIR /crawler
# Install dependencies # Install dependencies

View File

@ -0,0 +1,4 @@
export interface Category {
category_id: number;
name: string;
}

View File

@ -0,0 +1,9 @@
export interface ContactPerson {
contact_person_id: number;
first_name: string;
last_name: string;
gender: string;
email: string;
phone: string;
vendor_id: number;
}

View File

@ -0,0 +1,7 @@
export interface CrawlingStatus {
process_id: number;
started_timestamp: Date;
combinations_to_crawl: number;
successful_crawls: number;
failed_crawls: number;
}

View File

@ -0,0 +1,4 @@
export interface Manufacturer {
manufacturer_id: number;
name: string;
}

View File

@ -7,6 +7,10 @@ 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';
import {FavoriteShop} from '../models/favoriteshop'; import {FavoriteShop} from '../models/favoriteshop';
import {ContactPerson} from '../models/contactperson';
import {Category} from '../models/category';
import {Manufacturer} from '../models/manufacturer';
import {CrawlingStatus} from '../models/crawlingstatus';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -381,4 +385,216 @@ export class ApiService {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/* ______ __ __ ____
/ ____/___ ____ / /_____ ______/ /_ / __ \___ ______________ ____ _____
/ / / __ \/ __ \/ __/ __ `/ ___/ __/ / /_/ / _ \/ ___/ ___/ __ \/ __ \/ ___/
/ /___/ /_/ / / / / /_/ /_/ / /__/ /_ / ____/ __/ / (__ ) /_/ / / / (__ )
\____/\____/_/ /_/\__/\__,_/\___/\__/ /_/ \___/_/ /____/\____/_/ /_/____/
*/
/**
* Gets a list of all contact persons
* @return Observable<ContactPerson[]> An observable list of contact persons
*/
getContactPersons(): Observable<ContactPerson[]> {
try {
return this.http.get<ContactPerson[]>((this.apiUrl + '/contactpersons'));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets the specified contact person by id
* @param id the id of the contact person to get info about
* @return Observable<ContactPerson> An observable containing a single contact person
*/
getContactPersonById(id: number): Observable<ContactPerson> {
try {
return this.http.get<ContactPerson>((this.apiUrl + '/contactpersons/' + id));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets the contact persons for the specified vendor
* @param vendorId the id of the vendor to get the contact persons for
* @return Observable<ContactPerson[]> An observable list of contact persons
*/
getContactPersonsByVendor(vendorId: number): Observable<ContactPerson[]> {
try {
return this.http.get<ContactPerson[]>((this.apiUrl + '/contactpersons/byvendor/' + vendorId));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Adds a contact person for the specified vendor
* @param vendorId The id of the vendor to mark as favorite
* @param firstName The given name of the contact person
* @param lastName The family name of the contact person
* @param gender The gender of the contact person
* @param email The email address of the contact person
* @param phone The phone number of the contact person
* @return Observable<any> The observable response of the api
*/
addContactPerson(vendorId: number, firstName: string, lastName: string, gender: string, email: string, phone: string): Observable<any> {
try {
return this.http.post((this.apiUrl + '/contactpersons'), JSON.stringify({
vendor_id: vendorId,
first_name: firstName,
last_name: lastName,
gender,
email,
phone
}));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Updates the specified contact person record
* @param contactId The id of the contact person record
* @param vendorId The id of the vendor to mark as favorite
* @param firstName The given name of the contact person
* @param lastName The family name of the contact person
* @param gender The gender of the contact person
* @param email The email address of the contact person
* @param phone The phone number of the contact person
* @return Observable<any> The observable response of the api
*/
updateContactPerson(contactId: number, vendorId: number, firstName: string, lastName: string, gender: string, email: string, phone: string): Observable<any> {
try {
return this.http.put((this.apiUrl + '/contactpersons/' + contactId), JSON.stringify({
vendor_id: vendorId,
first_name: firstName,
last_name: lastName,
gender,
email,
phone
}));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/* ______ __ _
/ ____/___ _/ /____ ____ _____ _____(_)__ _____
/ / / __ `/ __/ _ \/ __ `/ __ \/ ___/ / _ \/ ___/
/ /___/ /_/ / /_/ __/ /_/ / /_/ / / / / __(__ )
\____/\__,_/\__/\___/\__, /\____/_/ /_/\___/____/
/____/
*/
/**
* Gets the specified category from the API
* @param id The id of the category to get
* @return Observable<Category> An observable containing a single category
*/
getCategoryById(id: number): Observable<Category> {
try {
return this.http.get<Category>((this.apiUrl + '/categories/' + id));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets a list of categories that match the given search term
* @param query The search term to match
* @return Observable<Category[]> An observable list of categories
*/
getCategoriesByQuery(query: string): Observable<Category[]> {
try {
return this.http.get<Category[]>((this.apiUrl + '/categories/search/' + query));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets a list of all categories
* @return Observable<Category[]> An observable list of categories
*/
getCategories(): Observable<Category[]> {
try {
return this.http.get<Category[]>((this.apiUrl + '/categories'));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/* __ ___ ____ __
/ |/ /___ _____ __ __/ __/___ ______/ /___ __________ __________
/ /|_/ / __ `/ __ \/ / / / /_/ __ `/ ___/ __/ / / / ___/ _ \/ ___/ ___/
/ / / / /_/ / / / / /_/ / __/ /_/ / /__/ /_/ /_/ / / / __/ / (__ )
/_/ /_/\__,_/_/ /_/\__,_/_/ \__,_/\___/\__/\__,_/_/ \___/_/ /____/
*/
/**
* Gets the specified manufacturer from the API
* @param id The id of the manufacturer to get
* @return Observable<Manufacturer> An observable containing a single manufacturer
*/
getManufacturerById(id: number): Observable<Manufacturer> {
try {
return this.http.get<Manufacturer>((this.apiUrl + '/manufacturers/' + id));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets a list of manufacturers that match the given search term
* @param query The search term to match
* @return Observable<Manufacturer[]> An observable list of manufacturers
*/
getManufacturersByQuery(query: string): Observable<Manufacturer[]> {
try {
return this.http.get<Manufacturer[]>((this.apiUrl + '/manufacturers/search/' + query));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets a list of all manufacturers
* @return Observable<Manufacturer[]> An observable list of manufacturer
*/
getManufacturers(): Observable<Manufacturer[]> {
try {
return this.http.get<Manufacturer[]>((this.apiUrl + '/manufacturers'));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/* ______ ___ _____ __ __
/ ____/________ __ __/ (_)___ ____ _ / ___// /_____ _/ /___ _______
/ / / ___/ __ `/ | /| / / / / __ \/ __ `/ \__ \/ __/ __ `/ __/ / / / ___/
/ /___/ / / /_/ /| |/ |/ / / / / / / /_/ / ___/ / /_/ /_/ / /_/ /_/ (__ )
\____/_/ \__,_/ |__/|__/_/_/_/ /_/\__, / /____/\__/\__,_/\__/\__,_/____/
/____/
*/
/**
* Gets the current crawling status
* @return Observable<CrawlingStatus> An observable containing a single crawling status object
*/
getCurrentCrawlingStatus(): Observable<CrawlingStatus> {
try {
return this.http.get<CrawlingStatus>((this.apiUrl + '/crawlingstatus'));
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
} }

View File

@ -5,6 +5,7 @@ Wiki: https://github.com/Mueller-Patrick/Betterzon/wiki
# Code Quality # Code Quality
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/88e47ebf837b43af9d12147c22f77f7f)](https://www.codacy.com/gh/Mueller-Patrick/Betterzon/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=Mueller-Patrick/Betterzon&amp;utm_campaign=Badge_Grade) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/88e47ebf837b43af9d12147c22f77f7f)](https://www.codacy.com/gh/Mueller-Patrick/Betterzon/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=Mueller-Patrick/Betterzon&amp;utm_campaign=Badge_Grade)
[![Code Coverage](https://img.shields.io/badge/coverage-82%25-green)](https://ci.betterzon.xyz)
# Project Status # Project Status
[![Website Status](https://img.shields.io/website?label=www.betterzon.xyz&style=for-the-badge&url=https%3A%2F%2Fwww.betterzon.xyz)](https://www.betterzon.xyz) [![Website Status](https://img.shields.io/website?label=www.betterzon.xyz&style=for-the-badge&url=https%3A%2F%2Fwww.betterzon.xyz)](https://www.betterzon.xyz)

File diff suppressed because it is too large Load Diff