Merge remote-tracking branch 'origin/master' into twitter_api

This commit is contained in:
Manuel Hude 2021-05-03 22:08:48 +02:00
commit b05df288d2
22 changed files with 212 additions and 18546 deletions

2
.gitignore vendored
View File

@ -6,6 +6,7 @@ node_modules/
venv/ venv/
env/ env/
container_db/
.DS_Store .DS_Store
@ -15,3 +16,4 @@ env/
.Trashes .Trashes
ehthumbs.db ehthumbs.db
Thumbs.db Thumbs.db
logfile

5
backend/.gitignore vendored
View File

@ -1,9 +1,8 @@
venv venv
*.pyc *.pyc
staticfiles staticfiles
media media/feed-icons
.env .env
*.sqlite3 app_be/db/
*.sqlite
waecm-2021-group-04.egg-info waecm-2021-group-04.egg-info
app_be/migrations app_be/migrations

View File

@ -1,19 +1,21 @@
FROM python:3.8-slim FROM python:3.8-slim
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
RUN apt-get update
RUN apt-get install -y build-essential gcc
RUN python -m venv /opt/venv RUN apt-get update; \
ENV PATH="/opt/venv/bin:$PATH" apt-get install -y build-essential gcc; \
python -m venv /opt/venv; \
RUN python -m pip install --upgrade pip python -m pip install --upgrade pip;
ENV PATH="/opt/venv/bin:$PATH" ENV PATH="/opt/venv/bin:$PATH"
RUN mkdir /code RUN mkdir /code
WORKDIR /code WORKDIR /code
COPY setup.py /code/ COPY setup.py /code/
COPY requirements.txt /code/ COPY requirements.txt /code/
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY . /code/ COPY . /code/

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -6,14 +6,10 @@ class User(models.Model):
pass pass
class Icon(models.Model):
image = models.ImageField(upload_to='feed_icons')
class Feed(models.Model): class Feed(models.Model):
url = models.TextField(blank=False, null=False, validators=[URLValidator(['http', 'https'])]) url = models.TextField(blank=False, null=False, validators=[URLValidator(['http', 'https'])])
active = models.BooleanField() active = models.BooleanField()
icon = models.FileField(upload_to='feed-icons', blank=True, null=True, icon = models.FileField(upload_to='feed-icons', blank=True, null=False, default='default-icon.svg',
validators=[FileExtensionValidator(['png', 'svg'])]) validators=[FileExtensionValidator(['png', 'svg'])])
keywords = models.TextField(blank=False, null=False) keywords = models.TextField(blank=False, null=False)
match_all_keywords = models.BooleanField(blank=True, default=False) match_all_keywords = models.BooleanField(blank=True, default=False)
@ -25,7 +21,7 @@ class FeedEntry(models.Model):
class Tweet(models.Model): class Tweet(models.Model):
icon = models.ForeignKey(Icon, on_delete=models.CASCADE, null=True) feed = models.ForeignKey(Feed, null=True, on_delete=models.SET_NULL)
text = models.CharField(max_length=137) text = models.CharField(max_length=137)
date_time = models.DateTimeField() date_time = models.DateTimeField()
url = models.CharField(max_length=100) url = models.CharField(max_length=100)

View File

@ -11,6 +11,10 @@ services:
context: ./backend context: ./backend
dockerfile: ./Dockerfile dockerfile: ./Dockerfile
command: python manage.py runserver 0.0.0.0:8000 command: python manage.py runserver 0.0.0.0:8000
env_file:
- .env
volumes:
- ./container_db:/code/app_be/db
ports: ports:
- 8000:8000 - 8000:8000

18466
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module'; import {AppRoutingModule} from './app-routing.module';
import {LandingComponent} from './component/landing/landing.component'; import {LandingComponent} from './component/landing/landing.component';
import {RestService} from './services/rest.service'; import {RestService} from './services/rest.service';
import {HttpClientModule} from '@angular/common/http'; import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {LoggerModule, NgxLoggerLevel} from 'ngx-logger'; import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
import {environment} from '../environments/environment'; import {environment} from '../environments/environment';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {FormsModule, ReactiveFormsModule} from '@angular/forms';
@ -30,6 +30,7 @@ import { EditierenComponent } from './component/einstellungen/editieren/editiere
import {MaterialFileInputModule} from 'ngx-material-file-input'; import {MaterialFileInputModule} from 'ngx-material-file-input';
import { DialogComponent } from './component/dialog/dialog.component'; import { DialogComponent } from './component/dialog/dialog.component';
import {MatDialogModule} from '@angular/material/dialog'; import {MatDialogModule} from '@angular/material/dialog';
import {InterceptorService} from './services/interceptor.service';
@NgModule({ @NgModule({
declarations: [LandingComponent, LoginComponent, NavigationComponent, declarations: [LandingComponent, LoginComponent, NavigationComponent,
@ -61,9 +62,9 @@ import {MatDialogModule} from '@angular/material/dialog';
AuthGuardService, AuthGuardService,
RestService, RestService,
JwtHelper, JwtHelper,
/*{ {
provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true
},*/ },
], ],
bootstrap: [LandingComponent] bootstrap: [LandingComponent]

View File

@ -3,6 +3,6 @@
<p>{{data.body}}</p> <p>{{data.body}}</p>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions mat-dialog-actions align="end"> <mat-dialog-actions mat-dialog-actions align="end">
<button mat-raised-button color="primary" [mat-dialog-close]="true">{{data.confirm}}</button>
<button *ngIf="data.hideAbort === false" mat-raised-button mat-dialog-close color="warn">{{data.abort}}</button> <button *ngIf="data.hideAbort === false" mat-raised-button mat-dialog-close color="warn">{{data.abort}}</button>
<button mat-raised-button color="primary" [mat-dialog-close]="true">{{data.confirm}}</button>
</mat-dialog-actions> </mat-dialog-actions>

View File

@ -6,17 +6,25 @@
<div class=" input-row"> <div class=" input-row">
<mat-form-field appearance="standard" class="input"> <mat-form-field appearance="standard" class="input">
<mat-label>Vollständige URL des RSS-Feeds</mat-label> <mat-label>Vollständige URL des RSS-Feeds</mat-label>
<input matInput formControlName="url" placeholder="https://rss.orf.at/news.xml" required> <input matInput type="url" formControlName="url" placeholder="https://rss.orf.at/news.xml" required>
<mat-error *ngIf="feedForm.get('url').hasError('invalidURLFormat')">Geben Sie eine valide URL ein.</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="input-row text-left"> <div class="input-row text-left">
<mat-form-field appearance="standard" class="input"> <mat-form-field appearance="standard" class="input">
<mat-label>Gesuchte Stichwörter</mat-label> <mat-label>Gesuchte Stichworte</mat-label>
<input matInput formControlName="keywords" placeholder="Spiel,Spaß,Schokolade" required> <input matInput formControlName="keywords" placeholder="Spiel,Spaß,Schokolade" required>
<mat-error *ngIf="feedForm.get('keywords').hasError('invalidKeywords')">Maximal 3 Wörter und jedes Wort
mindestens 3 Zeichen
</mat-error>
</mat-form-field> </mat-form-field>
<mat-checkbox formControlName="match_all_keywords">Alle Stichworte müssen enthalten sein</mat-checkbox> <mat-checkbox formControlName="match_all_keywords">Alle Stichworte müssen enthalten sein</mat-checkbox>
</div> </div>
<div class="input-row text-left"> <div class="row" style="padding: 10px 0">
<div class="col-auto" *ngIf="this.id">
<img class="feed-icon" src="{{this.icon}}" alt="Feed-Icon">
</div>
<div class="col">
<mat-form-field class="col"> <mat-form-field class="col">
<ngx-mat-file-input accept=".png,.svg" formControlName="icon" placeholder="Optionales Icon" <ngx-mat-file-input accept=".png,.svg" formControlName="icon" placeholder="Optionales Icon"
></ngx-mat-file-input> ></ngx-mat-file-input>
@ -24,16 +32,17 @@
<mat-error *ngIf="feedForm.get('icon').hasError('maxContentSize')"> <mat-error *ngIf="feedForm.get('icon').hasError('maxContentSize')">
Die maximale Dateigröße ist {{feedForm.get('icon')?.getError('maxContentSize').maxSize | byteFormat}} Die maximale Dateigröße ist {{feedForm.get('icon')?.getError('maxContentSize').maxSize | byteFormat}}
({{feedForm.get('icon')?.getError('maxContentSize').actualSize ({{feedForm.get('icon')?.getError('maxContentSize').actualSize
| byteFormat}}). | byteFormat}})
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
</div>
<div class="input-row text-left"> <div class="input-row text-left">
<mat-slide-toggle color="primary" formControlName="active">Feed als aktiv markieren</mat-slide-toggle> <mat-slide-toggle color="primary" formControlName="active">Feed als aktiv markieren</mat-slide-toggle>
</div> </div>
<div class="input-row margin-auto einstellungen_buttons_wrapper"> <div class="input-row margin-auto einstellungen_buttons_wrapper">
<button routerLink="/einstellungen" mat-raised-button>Abbrechen</button> <button routerLink="/einstellungen" mat-raised-button>Abbrechen</button>
<button mat-raised-button (click)="saveDialog(feedForm.value)" [disabled]="feedForm.invalid"> <button mat-raised-button (click)="saveFeed(feedForm.value)" [disabled]="feedForm.invalid">
<mat-icon>save</mat-icon> <mat-icon>save</mat-icon>
Speichern Speichern
</button> </button>

View File

@ -1,6 +1,6 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {FormGroup, FormBuilder} from '@angular/forms'; import {FormGroup, FormBuilder, AbstractControl} from '@angular/forms';
import {FileInput, FileValidator} from 'ngx-material-file-input'; import {FileInput, FileValidator} from 'ngx-material-file-input';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {environment} from '../../../../environments/environment'; import {environment} from '../../../../environments/environment';
@ -9,6 +9,8 @@ import {NGXLogger} from 'ngx-logger';
import {MatSnackBar} from '@angular/material/snack-bar'; import {MatSnackBar} from '@angular/material/snack-bar';
import {DialogComponent} from '../../dialog/dialog.component'; import {DialogComponent} from '../../dialog/dialog.component';
import {MatDialog} from '@angular/material/dialog'; import {MatDialog} from '@angular/material/dialog';
import {URLFormatValidator} from '../../../validators/url.validator';
import {keywordsValidator} from '../../../validators/keywords.validator';
@Component({ @Component({
selector: 'app-editieren', selector: 'app-editieren',
@ -44,10 +46,10 @@ export class EditierenComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.feedForm = this.formBuilder.group({ this.feedForm = this.formBuilder.group({
url: this.url, url: [this.url, [URLFormatValidator]],
active: this.active, active: this.active,
icon: [undefined, [FileValidator.maxContentSize(this.maxSize)]], icon: [undefined, [FileValidator.maxContentSize(this.maxSize)]],
keywords: this.keywords, keywords: [this.keywords, [keywordsValidator]],
match_all_keywords: this.match_all_keywords match_all_keywords: this.match_all_keywords
}); });
} }
@ -55,9 +57,9 @@ export class EditierenComponent implements OnInit {
public loadFeed(id) { public loadFeed(id) {
this.http.get('http://127.0.0.1:8000/feeds/' + id + '/').subscribe( this.http.get('http://127.0.0.1:8000/feeds/' + id + '/').subscribe(
(data: any) => { (data: any) => {
this._logger.debug('Data: ' + JSON.stringify(data));
this.url = data.url; this.url = data.url;
this.active = data.active; this.active = data.active;
this.icon = data.icon;
this.keywords = data.keywords; this.keywords = data.keywords;
this.match_all_keywords = data.match_all_keywords; this.match_all_keywords = data.match_all_keywords;
this.feedForm.patchValue({ this.feedForm.patchValue({
@ -66,10 +68,11 @@ export class EditierenComponent implements OnInit {
keywords: data.keywords, keywords: data.keywords,
match_all_keywords: data.match_all_keywords match_all_keywords: data.match_all_keywords
}); });
this._logger.debug('Icon in loadFeed' + this.icon);
}, },
err => this._logger.error(err), err => {
() => this._logger.debug('Loaded feed with ID ' + id) this.errorDialog();
this._logger.error(err);
}
); );
} }
@ -78,7 +81,6 @@ export class EditierenComponent implements OnInit {
form.append('url', feedData.url); form.append('url', feedData.url);
form.append('active', feedData.active); form.append('active', feedData.active);
form.append('match_all_keywords', feedData.match_all_keywords); form.append('match_all_keywords', feedData.match_all_keywords);
console.log('Match all: ' + feedData.match_all_keywords);
if (feedData.keywords != null) { if (feedData.keywords != null) {
form.append('keywords', feedData.keywords); form.append('keywords', feedData.keywords);
} }
@ -91,6 +93,7 @@ export class EditierenComponent implements OnInit {
this._snackbar.open('Feed erfolgreich gespeichert!', 'Schließen', {duration: 3000}); this._snackbar.open('Feed erfolgreich gespeichert!', 'Schließen', {duration: 3000});
}, },
err => { err => {
this.errorDialog();
this._logger.error(err); this._logger.error(err);
return throwError(err); return throwError(err);
} }
@ -101,6 +104,7 @@ export class EditierenComponent implements OnInit {
this._snackbar.open('Feed erfolgreich gespeichert!', 'Schließen', {duration: 3000}); this._snackbar.open('Feed erfolgreich gespeichert!', 'Schließen', {duration: 3000});
}, },
err => { err => {
this.errorDialog();
this._logger.error(err); this._logger.error(err);
return throwError(err); return throwError(err);
} }
@ -108,24 +112,16 @@ export class EditierenComponent implements OnInit {
} }
} }
public saveDialog(feedData) {
public errorDialog() {
const dialogRef = this._dialog.open(DialogComponent, { const dialogRef = this._dialog.open(DialogComponent, {
data: { data: {
title: 'Neuen Feed erstellen', title: 'Serverfehler',
body: 'Neuen Feed erstellen?', body: 'Fehler beim Aufruf des Servers',
abort: 'Abbrechen', abort: 'Abbrechen',
confirm: 'OK', confirm: 'OK',
hideAbort: false hideAbort: true
} }
}); });
dialogRef.afterClosed().subscribe(
data => {
if (data === true) {
// Confirm button was clicked
this.saveFeed(feedData);
}
}
);
} }
} }

View File

@ -25,3 +25,10 @@
margin-right: auto; margin-right: auto;
max-width: 800px; max-width: 800px;
} }
.notActive {
background-color: white;
filter:alpha(opacity=50); /* IE */
opacity: 0.5; /* Safari, Opera */
-moz-opacity:0.50; /* FireFox */
}

View File

@ -2,7 +2,7 @@
<div class="content"> <div class="content">
<div class="text-center"> <div class="text-center">
<div class="container" *ngFor="let feed of feeds"> <div class="container" *ngFor="let feed of feeds">
<div class="row feed-list-row"> <div class="row feed-list-row" [ngClass]="{'notActive': feed.active == false}">
<div class="col-2 text-center padding-0 margin-auto"> <div class="col-2 text-center padding-0 margin-auto">
<img class="feed-icon" src="{{feed.icon}}" alt="Feed-Icon"> <img class="feed-icon" src="{{feed.icon}}" alt="Feed-Icon">
</div> </div>
@ -15,13 +15,13 @@
</button> </button>
</div> </div>
<div class="col-1 padding-0 margin-auto"> <div class="col-1 padding-0 margin-auto">
<button mat-icon-button (click)="deleteFeed(feed.id)"> <button mat-icon-button (click)="deleteDialog(feed.id)">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="input-row margin-auto einstellungen_buttons_wrapper"> <div class="input-row margin-auto einstellungen_buttons_wrapper" *ngIf="this.feeds.length < 3">
<button routerLink="/einstellungen/editieren" mat-raised-button> <button routerLink="/einstellungen/editieren" mat-raised-button>
<mat-icon>add</mat-icon> RSS-Feed hinzufügen <mat-icon>add</mat-icon> RSS-Feed hinzufügen
</button> </button>

View File

@ -1,11 +1,13 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {Observable, throwError} from 'rxjs'; import {throwError} from 'rxjs';
import {MatSnackBar} from '@angular/material/snack-bar'; import {MatSnackBar} from '@angular/material/snack-bar';
import {NGXLogger} from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
import {IFeed} from '../../interfaces/feed.interface'; import {IFeed} from '../../interfaces/feed.interface';
import {FeedService} from '../../services/feed.service'; import {FeedService} from '../../services/feed.service';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {DialogComponent} from '../dialog/dialog.component';
import {MatDialog} from '@angular/material/dialog';
@Component({ @Component({
selector: 'app-einstellungen', selector: 'app-einstellungen',
@ -16,36 +18,65 @@ export class EinstellungenComponent implements OnInit {
icon; icon;
feeds: Observable<IFeed[]>; feeds: IFeed[] = [];
constructor(private http: HttpClient, constructor(private http: HttpClient,
private _snackbar: MatSnackBar, private _snackbar: MatSnackBar,
private _logger: NGXLogger, private _logger: NGXLogger,
private _feedService: FeedService, private _feedService: FeedService,
private _route: ActivatedRoute, private _route: ActivatedRoute,
private _router: Router) { private _dialog: MatDialog) {
this.icon = 'assets/logo.svg'; this.icon = 'assets/logo.svg';
this._feedService.getFeeds().subscribe( this._feedService.getFeeds().toPromise().then(data => this.feeds = data).catch(err => this._logger.error(err));
(data: any) => {
this.feeds = data;
}
);
} }
ngOnInit(): void { ngOnInit(): void {
} }
deleteFeed(id: number) { deleteFeed(id: number) {
console.log('Got number for deletion: ' + id);
this.http.delete('http://127.0.0.1:8000/feeds/' + id + '/').subscribe( this.http.delete('http://127.0.0.1:8000/feeds/' + id + '/').subscribe(
() => { () => {
this._snackbar.open('Feed erfolgreich gelöscht!', 'Schließen', {duration: 3000}); this._snackbar.open('Feed erfolgreich gelöscht!', 'Schließen', {duration: 3000});
this.feeds = this.feeds.filter(feed => feed.id !== id);
}, },
err => { err => {
this._logger.error(err); this._logger.error(err);
return throwError(err); return throwError(err);
} }
); );
this._router.navigate([this._router.url]); }
public deleteDialog(id: number) {
const dialogRef = this._dialog.open(DialogComponent, {
data: {
title: 'Feed löschen',
body: 'Feed wirklich löschen?',
abort: 'Abbrechen',
confirm: 'OK',
hideAbort: false
}
});
dialogRef.afterClosed().subscribe(
data => {
if (data === true) {
// Confirm button was clicked
this.deleteFeed(id);
}
}
);
}
public errorDialog() {
const dialogRef = this._dialog.open(DialogComponent, {
data: {
title: 'Serverfehler',
body: 'Fehler beim Aufruf des Servers',
abort: 'Abbrechen',
confirm: 'OK',
hideAbort: true
}
});
} }
} }

View File

@ -1,16 +1,16 @@
<app-navigation [activeLink]="'tweets'"></app-navigation> <app-navigation [activeLink]="'tweets'"></app-navigation>
<div class="content"> <div class="content">
<div class="text-center"> <div class="text-center" *ngIf="feeds.length === 0">
<span>Kein RSS_Feed vorhanden</span> <span>Kein RSS-Feed vorhanden</span>
<br> <br>
<span><a routerLink="/einstellungen/editieren">RSS-Feed erstellen</a></span> <a routerLink="/einstellungen/editieren">RSS-Feed erstellen</a>
</div> </div>
<div class="text-center"> <div class="text-center" *ngIf="feeds.length !== 0">
<span>Kein Tweets vorhanden</span> <span>Keine Tweets vorhanden</span>
<br> <br>
<span>Schau später noch einmal vorbei!</span> <span>Schau später noch einmal vorbei!</span>
</div> </div>
<div> <div *ngIf="feeds.length !== 0">
<div> <div>
<div class="container" *ngFor="let number of [1, 2, 3]"> <div class="container" *ngFor="let number of [1, 2, 3]">
<div class="row"> <div class="row">

View File

@ -1,6 +1,9 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {AuthService} from '../../services/auth.service'; import {AuthService} from '../../services/auth.service';
import {HttpClient, HttpHeaders} from '@angular/common/http'; import {HttpClient, HttpHeaders} from '@angular/common/http';
import {FeedService} from '../../services/feed.service';
import {IFeed} from '../../interfaces/feed.interface';
import {Observable} from 'rxjs';
class Tweet { class Tweet {
@ -14,9 +17,16 @@ class Tweet {
export class TweetsComponent implements OnInit { export class TweetsComponent implements OnInit {
tweets: Tweet[] = []; tweets: Tweet[] = [];
feeds: Observable<IFeed>[] = [];
constructor(private http: HttpClient, constructor(private http: HttpClient,
private authService: AuthService) { private authService: AuthService,
private _feedService: FeedService) {
this._feedService.getFeeds().subscribe(
(data: any) => {
this.feeds = data;
}
);
} }
ngOnInit(): void { ngOnInit(): void {

View File

@ -27,12 +27,18 @@ export class AuthService {
return sessionStorage.getItem(this.tokenKey); return sessionStorage.getItem(this.tokenKey);
} }
// Check whether the token is expired and return /**
// true or false * Check whether the token is expired and return true or false
* If false, removes token from sessionStorage.
*/
public isAuthenticated(): boolean { public isAuthenticated(): boolean {
const token = sessionStorage.getItem(this.tokenKey); const token = sessionStorage.getItem(this.tokenKey);
if (token) { if (token) {
return !this.jwtHelper.isTokenExpired(token); const authenticated = !this.jwtHelper.isTokenExpired(token);
if (!authenticated) {
sessionStorage.removeItem(this.tokenKey);
}
return authenticated;
} else { } else {
return false; return false;
} }

View File

@ -13,7 +13,6 @@ export class FeedService {
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
getFeeds(): Observable<IFeed[]> { getFeeds(): Observable<IFeed[]> {
console.log('FeedService called');
return this.http.get<IFeed[]>(this.url).pipe(delay(200)); return this.http.get<IFeed[]>(this.url).pipe(delay(200));
} }
} }

View File

@ -1,9 +1,9 @@
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {tap} from 'rxjs/operators';
import {NGXLogger} from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
import {AuthService} from './auth.service'; import {AuthService} from './auth.service';
import {Router} from '@angular/router';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -13,41 +13,28 @@ import {AuthService} from './auth.service';
*/ */
export class InterceptorService implements HttpInterceptor { export class InterceptorService implements HttpInterceptor {
constructor(private logger: NGXLogger, private authService: AuthService) { constructor(private logger: NGXLogger,
private authService: AuthService,
private router: Router
) {
} }
/** /**
* Intercepts every HTTP request and for example adds a token * Intercepts every HTTP request.
* If the token is invalid (expired) the user is redirected to logout (always).
* @param req http request * @param req http request
* @param next http response handler * @param next http response handler
*/ */
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// FOR TESTING ONLY - sets an expired token
// sessionStorage.setItem('loginToken', 'eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTk4ODYzNTF9.e83okQ1NHjeO-AxaB3SQP9P5UjOYy-e5DFissDdf2Mo');
const token = this.authService.getToken(); const authenticated = this.authService.isAuthenticated();
this.logger.debug('isAuthenticated?', authenticated);
// the original request is immutable, so we need to clone it if (!this.authService.isAuthenticated()) {
req = req.clone({ this.logger.debug('token invalid, route to login');
setHeaders: { this.router.navigate(['login']).then(() => {});
// FIXME e.g. if Bearer is used
Authorization: `Bearer ${token}`
} }
}); return next.handle(req);
this.logger.debug('Interceptor works');
// pipe the response observable
return next.handle(req).pipe(
// perform a side effect for every emission on the source Observable
// return an Observable that is identical to the source
tap(event => {
// check if it is the response message
if (event instanceof HttpResponse) {
// TODO so something if necessary
}
}, error => {
// TODO handle error here
this.logger.error('HTTP Response interception error', error);
})
);
} }
} }

View File

@ -0,0 +1,19 @@
import {AbstractControl} from '@angular/forms';
export function keywordsValidator(control: AbstractControl): { [key: string]: any } | null {
if (control.value == undefined) {
return null;
}
let split: string[];
split = control.value.split(',');
if (split.length > 3) {
return {'invalidKeywords': {value: control.value}};
}
for (let i = 0; i < split.length; i++) {
const word = split[i].trim();
if (word.length < 3) {
return {'invalidKeywords': {value: control.value}};
}
}
return null;
}

View File

@ -0,0 +1,6 @@
import {AbstractControl} from '@angular/forms';
export function URLFormatValidator(control: AbstractControl): {[key: string]: any} | null {
const valid = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/.test(control.value);
return valid ? null : {'invalidURLFormat': {value: control.value}};
}