Compare commits

...

17 Commits

Author SHA1 Message Date
henningxtro
ce92abdb40
Doku (#88)
* Added AC-ManageVendorShop.drawio

* Update AC-ManageVendorShop.drawio

* Added AC-ManageVendorShop.png

* Added AC_VendorShop.drawio

* Added Deployment_View.drawio

* Update Deployment_View.drawio

* Added AC_FavoriteShopList.drawio

* Added Deployment_View.png

* Added AC_FavoriteShopList.png

* Added ERM.png

* Adding architectural view

* Updated Use-Case-Diagram

Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>
Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
2021-06-15 13:05:33 +02:00
0cd1213c40 BETTERZON-146: Session handling rewrite HOTFIX 2 2021-06-15 12:09:04 +02:00
1fd115c2a2 BETTERZON-146: Session handling rewrite HOTFIX 2021-06-15 11:50:36 +02:00
Patrick
0118ac6861
Merge pull request #84 from Mueller-Patrick/BETTERZON-140
BETTERZON-140: Product Details Part 1
2021-06-15 11:34:00 +02:00
bf56d2b509 BETTERZON-146: Changing session handling from cookies to localStorage 2021-06-15 11:32:48 +02:00
ad091954c1 Merge remote-tracking branch 'origin/develop' into BETTERZON-140 2021-06-15 10:39:15 +02:00
b062e14c9a auth with cookies. 2021-06-15 10:38:54 +02:00
Patrick
f1dcaef351
Merge pull request #83 from Mueller-Patrick/BETTERZON-145
BETTERZON-145: Adding better error when trying to get session details…
2021-06-15 10:38:52 +02:00
391a4b5e4b BETTERZON-145: Adding better error when trying to get session details without cookie 2021-06-15 10:37:14 +02:00
cf300cb1b7 Merge remote-tracking branch 'origin/develop' into BETTERZON-140 2021-06-15 10:24:51 +02:00
Patrick
c35097a9a2
Merge pull request #82 from Mueller-Patrick/BETTERZON-144
BETTERZON-144: Adding service method to get session / user info
2021-06-15 10:24:20 +02:00
62795fd3f8 Merge remote-tracking branch 'origin/develop' into BETTERZON-140 2021-06-15 10:24:02 +02:00
Patrick
4a0ba40da6
Merge branch 'develop' into BETTERZON-144 2021-06-15 10:20:17 +02:00
f4d1e93a7f BETTERZON-144: Adding service method to get session / user info 2021-06-15 10:19:48 +02:00
daef6ec208 BETTERZON-141: Fixing user updating query 2021-06-13 14:16:09 +02:00
f2adb1e375 BETTERZON-141: Fixing service class
- Because Patrick was stupid
2021-06-13 13:09:51 +02:00
68e9d75e2d wip: register api 2021-06-13 12:49:15 +02:00
21 changed files with 283 additions and 73 deletions

View File

@ -76,7 +76,9 @@ contactpersonsRouter.post('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get required parameters // Get required parameters
const vendor_id = req.body.vendor_id; const vendor_id = req.body.vendor_id;
@ -104,7 +106,9 @@ contactpersonsRouter.put('/:id', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get required parameters // Get required parameters
const contact_person_id = parseInt(req.params.id, 10); const contact_person_id = parseInt(req.params.id, 10);

View File

@ -25,7 +25,9 @@ crawlingstatusRouter.get('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = (req.query.session_id ?? '').toString();
const session_key = (req.query.session_key ?? '').toString();
const user = await UserService.checkSession(session_id, session_key, user_ip);
if (!user.is_admin) { if (!user.is_admin) {
res.status(403).send({}); res.status(403).send({});

View File

@ -24,7 +24,9 @@ favoriteshopsRouter.get('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = (req.query.session_id ?? '').toString();
const session_key = (req.query.session_key ?? '').toString();
const user = await UserService.checkSession(session_id, session_key, user_ip);
const priceAlarms = await FavoriteShopsService.getFavoriteShops(user.user_id); const priceAlarms = await FavoriteShopsService.getFavoriteShops(user.user_id);
@ -40,7 +42,9 @@ favoriteshopsRouter.post('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get info for price alarm creation // Get info for price alarm creation
const vendor_id = req.body.vendor_id; const vendor_id = req.body.vendor_id;
@ -72,7 +76,9 @@ favoriteshopsRouter.delete('/:id', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = (req.query.session_id ?? '').toString();
const session_key = (req.query.session_key ?? '').toString();
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get info for price alarm creation // Get info for price alarm creation
const favorite_id = parseInt(req.params.id, 10); const favorite_id = parseInt(req.params.id, 10);

View File

@ -24,7 +24,9 @@ pricealarmsRouter.get('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = (req.query.session_id ?? '').toString();
const session_key = (req.query.session_key ?? '').toString();
const user = await UserService.checkSession(session_id, session_key, user_ip);
const priceAlarms = await PriceAlarmsService.getPriceAlarms(user.user_id); const priceAlarms = await PriceAlarmsService.getPriceAlarms(user.user_id);
@ -40,7 +42,9 @@ pricealarmsRouter.post('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get info for price alarm creation // Get info for price alarm creation
const product_id = req.body.product_id; const product_id = req.body.product_id;
@ -73,7 +77,9 @@ pricealarmsRouter.put('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get info for price alarm creation // Get info for price alarm creation
const alarm_id = req.body.alarm_id; const alarm_id = req.body.alarm_id;

View File

@ -107,7 +107,9 @@ pricesRouter.post('/', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get required parameters // Get required parameters
const vendor_id = req.body.vendor_id; const vendor_id = req.body.vendor_id;

View File

@ -47,10 +47,10 @@ usersRouter.post('/register', async (req: Request, res: Response) => {
const session: Session = await UserService.createUser(username, password, email, ip); const session: Session = await UserService.createUser(username, password, email, ip);
// Send the session details back to the user // Send the session details back to the user
res.cookie('betterauth', JSON.stringify({ res.status(201).send({
id: session.session_id, session_id: session.session_id,
key: session.session_key session_key: session.session_key
}), {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.'}));
@ -80,10 +80,10 @@ usersRouter.post('/login', async (req: Request, res: Response) => {
} }
// Send the session details back to the user // Send the session details back to the user
res.cookie('betterauth', JSON.stringify({ res.status(200).send({
id: session.session_id, session_id: session.session_id,
key: session.session_key session_key: session.session_key
}), {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.'}));
@ -94,9 +94,17 @@ usersRouter.post('/login', async (req: Request, res: Response) => {
usersRouter.post('/checkSessionValid', async (req: Request, res: Response) => { usersRouter.post('/checkSessionValid', async (req: Request, res: Response) => {
try { try {
const ip: string = req.connection.remoteAddress ?? ''; const ip: string = req.connection.remoteAddress ?? '';
const session_id = req.body.session_id;
const session_key = req.body.session_key;
if(!session_id || !session_key) {
// Error logging in, probably wrong username / password
res.status(401).send(JSON.stringify({messages: ['No session detected'], codes: [5]}));
return;
}
// Update the user entry and create a session // Update the user entry and create a session
const user: User = await UserService.checkSessionWithCookie(req.cookies.betterauth, ip); const user: User = await UserService.checkSession(session_id, session_key, ip);
if (!user.user_id) { if (!user.user_id) {
// Error logging in, probably wrong username / password // Error logging in, probably wrong username / password

View File

@ -115,8 +115,8 @@ export const login = async (username: string, password: string, ip: string): Pro
const sessionKeyHash = bcrypt.hashSync(sessionKey, 10); const sessionKeyHash = bcrypt.hashSync(sessionKey, 10);
// Update user entry in SQL // Update user entry in SQL
const userQuery = 'UPDATE users SET last_login_date = NOW()'; const userQuery = 'UPDATE users SET last_login_date = NOW() WHERE user_id = ?';
const userIdRes = await conn.query(userQuery); const userIdRes = await conn.query(userQuery, userId);
await conn.commit(); await conn.commit();
// Create session // Create session

View File

@ -37,7 +37,9 @@ vendorsRouter.get('/managed', async (req: Request, res: Response) => {
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = (req.query.session_id ?? '').toString();
const session_key = (req.query.session_key ?? '').toString();
const user = await UserService.checkSession(session_id, session_key, user_ip);
const vendors = await VendorService.getManagedShops(user.user_id); const vendors = await VendorService.getManagedShops(user.user_id);
@ -91,7 +93,9 @@ vendorsRouter.put('/manage/deactivatelisting', async (req: Request, res: Respons
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get required parameters // Get required parameters
const vendor_id = req.body.vendor_id; const vendor_id = req.body.vendor_id;
@ -115,7 +119,9 @@ vendorsRouter.put('/manage/shop/deactivate/:id', async (req: Request, res: Respo
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get required parameters // Get required parameters
const vendor_id = parseInt(req.params.id, 10); const vendor_id = parseInt(req.params.id, 10);
@ -138,7 +144,9 @@ vendorsRouter.put('/manage/shop/activate/:id', async (req: Request, res: Respons
try { try {
// Authenticate user // Authenticate user
const user_ip = req.connection.remoteAddress ?? ''; const user_ip = req.connection.remoteAddress ?? '';
const user = await UserService.checkSessionWithCookie(req.cookies.betterauth, user_ip); const session_id = req.body.session_id;
const session_key = req.body.session_key;
const user = await UserService.checkSession(session_id, session_key, user_ip);
// Get required parameters // Get required parameters
const vendor_id = parseInt(req.params.id, 10); const vendor_id = parseInt(req.params.id, 10);

View File

@ -61,7 +61,7 @@ form{
} }
.btn_signin{ .btn_signin{
transition: all .5s ease; transition: all .5s ease;
width: 70%; width: 100%;
border-radius: 30px; border-radius: 30px;
color:#008080; color:#008080;
font-weight: 600; font-weight: 600;

View File

@ -9,27 +9,30 @@
<h2>Konto erstellen</h2> <h2>Konto erstellen</h2>
</div> </div>
<div class="row"> <div class="row">
<form control="" class="form-group"> <form [formGroup]="form" class="form-group" (ngSubmit)="onSubmit()">
<div class="row"> <div class="row">
<input type="text" name="username" id="username" class="form__input" placeholder="Nickname"> <input type="text" formControlName="username" id="username" name="username" class="form__input" placeholder="Username">
<div *ngIf="submitted && me.username.errors" class="invalid-feedback">
<div *ngIf="me.username.errors.required">Username is required</div>
</div>
</div> </div>
<div class="row"> <div class="row">
<!-- <span class="fa fa-lock"></span> --> <!-- <span class="fa fa-lock"></span> -->
<input type="password" name="password" id="email" class="form__input" placeholder= "E-Mail"> <input type="email" formControlName="email" name="email" id="email" class="form__input" placeholder= "E-Mail">
</div> </div>
<div class="row"> <div class="row">
<!-- <span class="fa fa-lock"></span> --> <!-- <span class="fa fa-lock"></span> -->
<input type="password" name="password" id="password" class="form__input" placeholder="Kennwort erstellen"> <input type="password" formControlName="password" name="password" id="password" class="form__input" placeholder="Kennwort">
</div> </div>
<!--
<div class="row"> <div class="row">
<!-- <span class="fa fa-lock"></span> -->
<input type="password" name="password" id="password_repeated" class="form__input" placeholder="Kennwort bestätigen"> <input type="password" name="password" id="password_repeated" class="form__input" placeholder="Kennwort bestätigen">
</div> </div> -->
<div class="row"> <div class="row">
<input type="submit" value="Erstellen" class="btn_signin"> <input type="submit" value="Erstellen" class="btn_signin">
</div> </div>
<div class="row"> <div class="row">
<p>Haben Sie bereits ein Konto?<a href="/signin">Sich anmelden</a></p> <a href="/signin">Sich anmelden</a>
</div> </div>
</form> </form>
</div> </div>

View File

@ -1,4 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {ApiService} from "../../../services/api.service";
@Component({ @Component({
selector: 'app-registration', selector: 'app-registration',
@ -6,10 +9,29 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./registration.component.css'] styleUrls: ['./registration.component.css']
}) })
export class RegistrationComponent implements OnInit { export class RegistrationComponent implements OnInit {
form: any;
loading = false;
submitted = false;
constructor() { } constructor(
private formBuilder: FormBuilder,
private api : ApiService
) { }
ngOnInit(): void { ngOnInit(): void {
this.form = this.formBuilder.group({
username: ['', Validators.required],
email: ['', Validators.required],
password: ['', [
Validators.required,
Validators.minLength(8)]
],
});
} }
get me() { return this.form.controls; }
onSubmit() {
this.api.registerUser(this.form.value.username, this.form.value.password, this.form.value.email).subscribe(res=>console.log(res));
}
} }

View File

@ -61,7 +61,7 @@ form{
} }
.btn_signin{ .btn_signin{
transition: all .5s ease; transition: all .5s ease;
width: 70%; width: 100%;
border-radius: 30px; border-radius: 30px;
color:#008080; color:#008080;
font-weight: 600; font-weight: 600;

View File

@ -9,13 +9,13 @@
<h2>Anmelden</h2> <h2>Anmelden</h2>
</div> </div>
<div class="row"> <div class="row">
<form control="" class="form-group"> <form [formGroup]="loginForm" class="form-group" (ngSubmit)="onSubmit()">
<div class="row"> <div class="row">
<input type="text" name="username" id="username" class="form__input" placeholder="Username"> <input type="text" formControlName="username" name="username" id="username" class="form__input" placeholder="Username">
</div> </div>
<div class="row"> <div class="row">
<!-- <span class="fa fa-lock"></span> --> <!-- <span class="fa fa-lock"></span> -->
<input type="password" name="password" id="password" class="form__input" placeholder="Password"> <input type="password" formControlName="password" name="password" id="password" class="form__input" placeholder="Password">
</div> </div>
<div class="row"> <div class="row">
<input type="submit" value="Anmelden" class="btn_signin"> <input type="submit" value="Anmelden" class="btn_signin">
@ -23,7 +23,7 @@
</form> </form>
</div> </div>
<div class="row"> <div class="row">
<p>Noch kein Konto?<a href="/registration">Konto erstellen</a></p> <a href="/registration">Konto erstellen</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,7 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {ApiService} from '../../../services/api.service';
import {Router} from '@angular/router';
@Component({ @Component({
selector: 'app-signin', selector: 'app-signin',
@ -8,12 +11,46 @@ import { Component, OnInit } from '@angular/core';
export class SigninComponent implements OnInit { export class SigninComponent implements OnInit {
constructor() { } loginForm: FormGroup;
loading = false;
submitted = false;
private isSuccessful: boolean;
private isSignUpFailed: boolean;
private errorMessage: '';
constructor(
private formBuilder: FormBuilder,
private api: ApiService,
private router: Router
) {
}
ngOnInit(): void { ngOnInit(): void {
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', [Validators.required, Validators.minLength(8)]]
});
} }
onSubmit() { onSubmit(): void {
this.submitted = true;
if (this.loginForm.invalid) {
return;
}
this.api.loginUser(this.loginForm.value.username, this.loginForm.value.password)
.subscribe(
data => {
this.router.navigate(['']);
this.isSuccessful = true;
this.isSignUpFailed = false;
this.api.saveSessionInfoToLocalStorage(data);
},
err => {
this.errorMessage = err.error.message;
this.isSignUpFailed = true;
});
} }
} }

View File

@ -9,7 +9,7 @@
<!-- Portfolio Grid Items--> <!-- Portfolio Grid Items-->
<div class="row justify-content-center"> <div class="row justify-content-center">
<!-- Portfolio Item 1--> <!-- Portfolio Item 1-->
<div class="col-md-4 mx-auto my-5" *ngFor="let product of products"> <div class="col-md-4 mx-auto my-5" *ngFor="let product of products" (click)="clickedProduct(product)">
<div class="bbb_deals_wrapper"> <div class="bbb_deals_wrapper">
<div class="bbb_deals_image"><img src="https://www.mueller-patrick.tech/betterzon/images/{{product.image_guid}}.jpg" alt=""></div> <div class="bbb_deals_image"><img src="https://www.mueller-patrick.tech/betterzon/images/{{product.image_guid}}.jpg" alt=""></div>
<div class="bbb_deals_content"> <div class="bbb_deals_content">
@ -17,7 +17,7 @@
<div class="bbb_deals_item_name">{{product.name}}</div> <div class="bbb_deals_item_name">{{product.name}}</div>
</div> </div>
<div class="bbb_deals_info_line d-flex flex-row justify-content-start"> <div class="bbb_deals_info_line d-flex flex-row justify-content-start">
<div class="bbb_deals_item_category">Amazon: <span id="bbb_deals_item_price_a"><strike>699.00$</strike></span></div> <div class="bbb_deals_item_category">Amazon: <span id="bbb_deals_item_price_a"><strike>{{product.price}}$</strike></span></div>
</div> </div>
<div class="bbb_deals_info_line d-flex flex-row justify-content-start"> <div class="bbb_deals_info_line d-flex flex-row justify-content-start">
<div class="bbb_deals_item_category">Plantshub: <span id="bbb_deals_item_price_b">599,00$</span></div> <div class="bbb_deals_item_category">Plantshub: <span id="bbb_deals_item_price_b">599,00$</span></div>

View File

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {ApiService} from "../../services/api.service";
@Component({ @Component({
selector: 'app-top-bar', selector: 'app-top-bar',
@ -9,9 +11,12 @@ export class TopBarComponent implements OnInit {
sidenav: any; sidenav: any;
constructor() { } constructor(
private api: ApiService
) { }
ngOnInit() { ngOnInit() {
}
this.api.getUserInfo().subscribe(data=>{console.log(data)});
}
} }

View File

@ -11,4 +11,5 @@ export interface Product {
manufacturer_id: number; manufacturer_id: number;
selling_rank: string; selling_rank: string;
category_id: number; category_id: number;
price: number;
} }

View File

@ -1,7 +1,7 @@
<app-top-bar></app-top-bar> <app-top-bar></app-top-bar>
<div id="mainComponents"> <div id="mainComponents">
<app-product-details [productId]="productId"></app-product-details> <app-product-details [productId]="productId"></app-product-details>
<app-newest-prices-list [productId]="productId"></app-newest-prices-list> <app-newest-prices-list [productId]="productId"></app-newest-prices-list>
</div> </div>
<app-bottom-bar></app-bottom-bar>

View File

@ -11,6 +11,7 @@ import {ContactPerson} from '../models/contactperson';
import {Category} from '../models/category'; import {Category} from '../models/category';
import {Manufacturer} from '../models/manufacturer'; import {Manufacturer} from '../models/manufacturer';
import {CrawlingStatus} from '../models/crawlingstatus'; import {CrawlingStatus} from '../models/crawlingstatus';
import {log} from 'util';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -242,7 +243,11 @@ export class ApiService {
*/ */
addNewPrice(vendorId: number, productId: number, price: number): Observable<any> { addNewPrice(vendorId: number, productId: number, price: number): Observable<any> {
try { try {
const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.post((this.apiUrl + '/prices'), JSON.stringify({ return this.http.post((this.apiUrl + '/prices'), JSON.stringify({
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
vendor_id: vendorId, vendor_id: vendorId,
product_id: productId, product_id: productId,
price_in_cents: price price_in_cents: price
@ -277,7 +282,13 @@ export class ApiService {
*/ */
getManagedVendors(): Observable<Vendor[]> { getManagedVendors(): Observable<Vendor[]> {
try { try {
return this.http.get<Vendor[]>((this.apiUrl + '/vendors/managed')); const sessionInfo = this.getSessionInfoFromLocalStorage();
let params = new HttpParams();
params = params.append('session_id', sessionInfo.session_id);
params = params.append('session_key', sessionInfo.session_key);
return this.http.get<Vendor[]>((this.apiUrl + '/vendors/managed'), {params});
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -317,10 +328,14 @@ export class ApiService {
*/ */
deactivateSingleVendorListing(vendorId: number, productId: number): Observable<any> { deactivateSingleVendorListing(vendorId: number, productId: number): Observable<any> {
try { try {
return this.http.put((this.apiUrl + '/vendors/manage/deactivatelisting'), JSON.stringify({ const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.put((this.apiUrl + '/vendors/manage/deactivatelisting'), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
vendor_id: vendorId, vendor_id: vendorId,
product_id: productId product_id: productId
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -333,7 +348,12 @@ export class ApiService {
*/ */
deactivateVendor(vendorId: number): Observable<any> { deactivateVendor(vendorId: number): Observable<any> {
try { try {
return this.http.put((this.apiUrl + '/vendors/manage/shop/deactivate/' + vendorId), JSON.stringify({})); const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.put((this.apiUrl + '/vendors/manage/shop/deactivate/' + vendorId), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
});
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -346,7 +366,12 @@ export class ApiService {
*/ */
activateVendor(vendorId: number): Observable<any> { activateVendor(vendorId: number): Observable<any> {
try { try {
return this.http.put((this.apiUrl + '/vendors/manage/shop/activate/' + vendorId), JSON.stringify({})); const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.put((this.apiUrl + '/vendors/manage/shop/activate/' + vendorId), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
});
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -366,7 +391,13 @@ export class ApiService {
*/ */
getPriceAlarms(): Observable<PriceAlarm[]> { getPriceAlarms(): Observable<PriceAlarm[]> {
try { try {
return this.http.get<PriceAlarm[]>((this.apiUrl + '/pricealarms')); const sessionInfo = this.getSessionInfoFromLocalStorage();
let params = new HttpParams();
params = params.append('session_id', sessionInfo.session_id);
params = params.append('session_key', sessionInfo.session_key);
return this.http.get<PriceAlarm[]>((this.apiUrl + '/pricealarms'), {params});
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -380,10 +411,14 @@ export class ApiService {
*/ */
createPriceAlarms(productId: number, definedPrice: number): Observable<any> { createPriceAlarms(productId: number, definedPrice: number): Observable<any> {
try { try {
return this.http.post((this.apiUrl + '/pricealarms'), JSON.stringify({ const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.post((this.apiUrl + '/pricealarms'), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
product_id: productId, product_id: productId,
defined_price: definedPrice defined_price: definedPrice
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -397,10 +432,14 @@ export class ApiService {
*/ */
updatePriceAlarms(alarmId: number, definedPrice: number): Observable<any> { updatePriceAlarms(alarmId: number, definedPrice: number): Observable<any> {
try { try {
return this.http.put((this.apiUrl + '/pricealarms'), JSON.stringify({ const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.put((this.apiUrl + '/pricealarms'), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
alarm_id: alarmId, alarm_id: alarmId,
defined_price: definedPrice defined_price: definedPrice
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -423,11 +462,11 @@ export class ApiService {
*/ */
registerUser(username: string, password: string, email: string): Observable<any> { registerUser(username: string, password: string, email: string): Observable<any> {
try { try {
return this.http.post((this.apiUrl + '/users/register'), JSON.stringify({ return this.http.post((this.apiUrl + '/users/register'), {
username, username,
password, password,
email email
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -441,15 +480,52 @@ export class ApiService {
*/ */
loginUser(username: string, password: string): Observable<any> { loginUser(username: string, password: string): Observable<any> {
try { try {
return this.http.post((this.apiUrl + '/users/login'), JSON.stringify({ return this.http.post((this.apiUrl + '/users/login'), {
username, username,
password password
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Get all required information about the currently logged in user. If the user is not logged in or the
* session is not valid anymore, a 401 will come back from the backend.
* @return Observable<any> The observable response of the api
*/
getUserInfo(): Observable<any> {
try {
const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.post((this.apiUrl + '/users/checkSessionValid'), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key
});
} catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
}
}
/**
* Gets session id and session key from local storage
* @return any {session_id: '', session_key: ''}
*/
getSessionInfoFromLocalStorage(): any {
const session_id = localStorage.getItem('session_id') ?? '';
const session_key = localStorage.getItem('session_key') ?? '';
return {session_id, session_key};
}
/**
* Extracts and saves the session data from an api response
* @param data The api response
*/
saveSessionInfoToLocalStorage(data: any): boolean {
localStorage.setItem('session_id', data.session_id);
localStorage.setItem('session_key', data.session_key);
return true;
}
/* ______ _ __ __ /* ______ _ __ __
/ ____/___ __ ______ _____(_) /____ _____/ /_ ____ ____ _____ / ____/___ __ ______ _____(_) /____ _____/ /_ ____ ____ _____
/ /_ / __ `/ | / / __ \/ ___/ / __/ _ \ / ___/ __ \/ __ \/ __ \/ ___/ / /_ / __ `/ | / / __ \/ ___/ / __/ _ \ / ___/ __ \/ __ \/ __ \/ ___/
@ -464,7 +540,13 @@ export class ApiService {
*/ */
getFavoriteShops(): Observable<FavoriteShop[]> { getFavoriteShops(): Observable<FavoriteShop[]> {
try { try {
return this.http.get<FavoriteShop[]>((this.apiUrl + '/favoriteshops')); const sessionInfo = this.getSessionInfoFromLocalStorage();
let params = new HttpParams();
params = params.append('session_id', sessionInfo.session_id);
params = params.append('session_key', sessionInfo.session_key);
return this.http.get<FavoriteShop[]>((this.apiUrl + '/favoriteshops'), {params});
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -477,9 +559,13 @@ export class ApiService {
*/ */
addFavoriteShop(vendorId: number): Observable<any> { addFavoriteShop(vendorId: number): Observable<any> {
try { try {
return this.http.post((this.apiUrl + '/favoriteshops'), JSON.stringify({ const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.post((this.apiUrl + '/favoriteshops'), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
vendor_id: vendorId vendor_id: vendorId
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -492,7 +578,13 @@ export class ApiService {
*/ */
deleteFavoriteShop(vendorId: number): Observable<any> { deleteFavoriteShop(vendorId: number): Observable<any> {
try { try {
return this.http.delete((this.apiUrl + '/favoriteshops/' + vendorId)); const sessionInfo = this.getSessionInfoFromLocalStorage();
let params = new HttpParams();
params = params.append('session_id', sessionInfo.session_id);
params = params.append('session_key', sessionInfo.session_key);
return this.http.delete((this.apiUrl + '/favoriteshops/' + vendorId), {params});
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -556,14 +648,18 @@ export class ApiService {
*/ */
addContactPerson(vendorId: number, firstName: string, lastName: string, gender: string, email: string, phone: string): Observable<any> { addContactPerson(vendorId: number, firstName: string, lastName: string, gender: string, email: string, phone: string): Observable<any> {
try { try {
return this.http.post((this.apiUrl + '/contactpersons'), JSON.stringify({ const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.post((this.apiUrl + '/contactpersons'), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
vendor_id: vendorId, vendor_id: vendorId,
first_name: firstName, first_name: firstName,
last_name: lastName, last_name: lastName,
gender, gender,
email, email,
phone phone
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -582,14 +678,18 @@ export class ApiService {
*/ */
updateContactPerson(contactId: number, vendorId: number, firstName: string, lastName: string, gender: string, email: string, phone: string): Observable<any> { updateContactPerson(contactId: number, vendorId: number, firstName: string, lastName: string, gender: string, email: string, phone: string): Observable<any> {
try { try {
return this.http.put((this.apiUrl + '/contactpersons/' + contactId), JSON.stringify({ const sessionInfo = this.getSessionInfoFromLocalStorage();
return this.http.put((this.apiUrl + '/contactpersons/' + contactId), {
session_id: sessionInfo.session_id,
session_key: sessionInfo.session_key,
vendor_id: vendorId, vendor_id: vendorId,
first_name: firstName, first_name: firstName,
last_name: lastName, last_name: lastName,
gender, gender,
email, email,
phone phone
})); });
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
@ -704,7 +804,13 @@ export class ApiService {
*/ */
getCurrentCrawlingStatus(): Observable<CrawlingStatus> { getCurrentCrawlingStatus(): Observable<CrawlingStatus> {
try { try {
return this.http.get<CrawlingStatus>((this.apiUrl + '/crawlingstatus')); const sessionInfo = this.getSessionInfoFromLocalStorage();
let params = new HttpParams();
params = params.append('session_id', sessionInfo.session_id);
params = params.append('session_key', sessionInfo.session_key);
return this.http.get<CrawlingStatus>((this.apiUrl + '/crawlingstatus'), {params});
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }

View File

@ -1 +1 @@
<mxfile host="app.diagrams.net" modified="2020-12-03T09:39:52.243Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" etag="RUzsjnuqX_zRTCHSTruo" version="13.10.6" type="github"><diagram id="QFWcWedTnleHV76omDGD" name="Page-1">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</diagram></mxfile> <mxfile host="Electron" modified="2021-06-15T10:10:23.336Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="xPXaWnl_FRrq1LtBykUU" version="13.4.5" type="device"><diagram id="QFWcWedTnleHV76omDGD" name="Page-1">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</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 85 KiB