Merge remote-tracking branch 'origin/master' into twitter_api
This commit is contained in:
commit
b05df288d2
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
5
backend/.gitignore
vendored
@ -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
|
||||||
|
|||||||
@ -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/
|
||||||
|
|||||||
BIN
backend/app_be/media/default-icon.svg
Normal file
BIN
backend/app_be/media/default-icon.svg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@ -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)
|
||||||
|
|||||||
@ -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
18466
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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]
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -6,34 +6,43 @@
|
|||||||
<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">
|
||||||
<mat-form-field class="col">
|
<div class="col-auto" *ngIf="this.id">
|
||||||
<ngx-mat-file-input accept=".png,.svg" formControlName="icon" placeholder="Optionales Icon"
|
<img class="feed-icon" src="{{this.icon}}" alt="Feed-Icon">
|
||||||
></ngx-mat-file-input>
|
</div>
|
||||||
<mat-icon matSuffix>folder</mat-icon>
|
<div class="col">
|
||||||
<mat-error *ngIf="feedForm.get('icon').hasError('maxContentSize')">
|
<mat-form-field class="col">
|
||||||
Die maximale Dateigröße ist {{feedForm.get('icon')?.getError('maxContentSize').maxSize | byteFormat}}
|
<ngx-mat-file-input accept=".png,.svg" formControlName="icon" placeholder="Optionales Icon"
|
||||||
({{feedForm.get('icon')?.getError('maxContentSize').actualSize
|
></ngx-mat-file-input>
|
||||||
| byteFormat}}).
|
<mat-icon matSuffix>folder</mat-icon>
|
||||||
</mat-error>
|
<mat-error *ngIf="feedForm.get('icon').hasError('maxContentSize')">
|
||||||
</mat-form-field>
|
Die maximale Dateigröße ist {{feedForm.get('icon')?.getError('maxContentSize').maxSize | byteFormat}}
|
||||||
|
({{feedForm.get('icon')?.getError('maxContentSize').actualSize
|
||||||
|
| byteFormat}})
|
||||||
|
</mat-error>
|
||||||
|
</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>
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 */
|
||||||
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
frontend/src/app/validators/keywords.validator.ts
Normal file
19
frontend/src/app/validators/keywords.validator.ts
Normal 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;
|
||||||
|
}
|
||||||
6
frontend/src/app/validators/url.validator.ts
Normal file
6
frontend/src/app/validators/url.validator.ts
Normal 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}};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user