Structural changes:

- AuthenticationService->AuthService
- removed underscore-directories
- put site related stuff into site/
- same for projector
- auth service went into a core/ directory, used by site and projector
- the alert is now not global anymore. Every view can have its own alert
- make the auth service query users/whoami for the current user
- made a new OpenSlides service for managing the startup
... A lot todo ...
This commit is contained in:
FinnStutzenstein 2018-06-16 18:05:46 +02:00
parent ed2e44b484
commit 3f78ba1f3d
44 changed files with 461 additions and 399 deletions

View File

@ -3,6 +3,14 @@
"target": "http://localhost:8000",
"secure": false
},
"/users/logout": {
"target": "http://localhost:8000",
"secure": false
},
"/users/whoami": {
"target": "http://localhost:8000",
"secure": false
},
"/rest": {
"target": "http://localhost:8000",
"secure": false

View File

@ -1,4 +0,0 @@
<div *ngFor="let alert of alerts" class="{{ cssClass(alert) }} alert-dismissable">
{{alert.message}}
<a class="close" (click)="removeAlert(alert)">&times;</a>
</div>

View File

@ -1,51 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Alert, AlertType } from '../../_models/alert';
import { AlertService } from '../../_services/alert.service';
@Component({
selector: 'alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.css']
})
export class AlertComponent implements OnInit {
alerts: Alert[] = [];
constructor(private alertService: AlertService) { }
ngOnInit() {
this.alertService.getAlert().subscribe((alert: Alert) => {
if (!alert) {
// clear alerts when an empty alert is received
this.alerts = [];
return;
}
// add alert to array
this.alerts.push(alert);
});
}
removeAlert(alert: Alert) {
this.alerts = this.alerts.filter(x => x !== alert);
}
cssClass(alert: Alert) {
if (!alert) {
return;
}
// return css class based on alert type
switch (alert.type) {
case AlertType.Success:
return 'alert alert-success';
case AlertType.Error:
return 'alert alert-danger';
case AlertType.Info:
return 'alert alert-info';
case AlertType.Warning:
return 'alert alert-warning';
}
}
}

View File

@ -1,45 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Alert, AlertType } from '../_models/alert';
@Injectable({
providedIn: 'root'
})
export class AlertService {
private subject = new Subject<Alert>();
constructor() { }
getAlert(): Observable<any> {
return this.subject.asObservable();
}
success(message: string) {
this.alert(AlertType.Success, message);
}
error(message: string) {
this.alert(AlertType.Error, message);
}
info(message: string) {
this.alert(AlertType.Info, message);
}
warn(message: string) {
this.alert(AlertType.Warning, message);
}
alert(type: AlertType, message: string) {
this.subject.next(<Alert>{ type: type, message: message });
}
clear() {
// clear alerts
this.subject.next();
}
}

View File

@ -1,34 +0,0 @@
import { Injectable } from '@angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import { AuthenticationService } from "./authentication.service";
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authenticationService: AuthenticationService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
console.log('AuthGuard#canActivate called');
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authenticationService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authenticationService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
}

View File

@ -1,69 +0,0 @@
import { Injectable } from '@angular/core';
import {
HttpClient, HttpResponse,
HttpErrorResponse, HttpHeaders
} from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, tap, delay } from 'rxjs/operators';
import { User } from '../users/user';
const httpOptions = {
withCredentials: true,
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
isLoggedIn: boolean;
loginURL: string = '/users/login/'
// store the URL so we can redirect after logging in
redirectUrl: string;
constructor(private http: HttpClient) {
//check for the cookie in local storrage
//TODO checks for username now since django does not seem to return a cookie
if(localStorage.getItem("username")) {
this.isLoggedIn = true;
} else {
this.isLoggedIn = false;
}
}
//loggins a users. expects a user model
loginUser(user: User): Observable<any> {
return this.http.post(this.loginURL, user, httpOptions)
.pipe(
tap(val => {
this.isLoggedIn = true;
//Set the session cookie in local storrage.
//TODO needs validation
}),
catchError(this.handleError())
);
}
//logout the user
//TODO not yet used
logoutUser(): void {
this.isLoggedIn = false;
localStorage.removeItem("username");
}
//very generic error handling function.
//implicitly returns an observable that will display an error message
private handleError<T>() {
return (error: any): Observable<T> => {
console.error(error);
return of(error);
}
};
}

View File

@ -1,21 +1,17 @@
import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '../base.component';
import { Title } from '@angular/platform-browser';
import { TitleService } from '../core/title.service';
@Component({
selector: 'app-agenda',
templateUrl: './agenda.component.html',
styleUrls: ['./agenda.component.css']
})
export class AgendaComponent extends BaseComponent implements OnInit {
export class AgendaComponent implements OnInit {
constructor(titleService: Title) {
super(titleService)
constructor(private titleService: TitleService) {
}
ngOnInit() {
//TODO translate
super.setTitle("Agenda");
this.titleService.setTitle("Agenda");
}
}

View File

@ -1,24 +1,28 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { ProjectorComponent } from "./projector/projector.component";
import { StartComponent } from "./start/start.component";
import { ProjectorContainerComponent } from "./projector/projector-container.component";
import { StartComponent } from "./site/start.component";
import { AgendaComponent } from "./agenda/agenda.component";
import { MotionsComponent } from "./motions/motions.component";
import { SiteComponent } from "./site/site.component";
import { LoginComponent } from './users/login.component';
import { AuthGuard } from "./_services/auth-guard.service";
import { RouterAuthGuard } from "./core/router-auth-guard.service";
const routes: Routes = [
{ path: 'projector', component: ProjectorComponent },
{ path: 'login', component: LoginComponent },
{ path: 'projector/:id', component: ProjectorComponent },
//{ path: 'projector', redirect: 'projector/1' }, // Test this
{ path: 'real-projector/:id', component: ProjectorContainerComponent },
//{ path: 'real-projector', redirect: 'real-projector/1' }, // this too
{
path: '',
component: SiteComponent,
canActivate: [AuthGuard],
canActivate: [RouterAuthGuard],
children: [
{ path: '', component: StartComponent },
{ path: 'login', component: LoginComponent },
{ path: 'agenda', component: AgendaComponent },
{ path: 'motions', component: MotionsComponent },
]

View File

@ -1,2 +1 @@
<router-outlet></router-outlet>

View File

@ -1,12 +1,15 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { OpenSlidesService } from './core/openslides.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'OpenSlides 3';
constructor() { }
export class AppComponent implements OnInit {
constructor(private openSlides: OpenSlidesService) { }
ngOnInit() {
this.openSlides.bootup();
}
}

View File

@ -7,16 +7,17 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { LoginComponent } from './users/login.component';
import { UsersComponent } from './users/users.component';
import { AppRoutingModule } from './/app-routing.module';
import { AppRoutingModule } from './app-routing.module';
import { ProjectorComponent } from './projector/projector.component';
import { ProjectorContainerComponent } from './projector/projector-container.component';
import { MotionsComponent } from './motions/motions.component';
import { AgendaComponent } from './agenda/agenda.component';
import { SiteComponent } from './site/site.component';
import { StartComponent } from './start/start.component';
import { AlertComponent } from './_directives/alert/alert.component';
import { AlertService } from './_services/alert.service';
import { StartComponent } from './site/start.component';
import { AlertComponent } from './site/alert.component';
import { AlertService } from './site/alert.service';
//add font-awesome icons to library.
//will blow up the code.
@ -28,6 +29,7 @@ library.add(fas);
LoginComponent,
UsersComponent,
ProjectorComponent,
ProjectorContainerComponent,
MotionsComponent,
AgendaComponent,
SiteComponent,

View File

@ -1,13 +0,0 @@
import { Title } from '@angular/platform-browser';
//provides functions that might be used by a lot of components
export abstract class BaseComponent {
private titleSuffix: string = " - OpenSlides 3";
constructor(protected titleService: Title) { }
setTitle(prefix: string) {
this.titleService.setTitle( prefix + this.titleSuffix );
}
}

View File

@ -0,0 +1,81 @@
import { Injectable } from '@angular/core';
import {
HttpClient,
HttpResponse,
HttpErrorResponse,
HttpHeaders
} from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, tap, delay } from 'rxjs/operators';
import { User } from '../users/user';
const httpOptions = {
withCredentials: true,
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
export interface LoginResponse {
user_id: number;
user: User;
}
export interface WhoAmIResponse extends LoginResponse {
guest_enabled: boolean;
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
isLoggedIn: boolean = false;
// store the URL so we can redirect after logging in
redirectUrl: string;
constructor(private http: HttpClient) { }
// Initialize the service by querying the server
init(): Observable<WhoAmIResponse | {}> {
return this.http.get<WhoAmIResponse>('users/whoami', httpOptions)
.pipe(
catchError(this.handleError())
);
}
loginUser(user: User): Observable<LoginResponse | {}> {
return this.http.post<LoginResponse>('users/login', user, httpOptions)
.pipe(
tap(val => {
console.log(val)
}),
catchError(this.handleError())
);
}
//logout the user
//TODO reboot openslides
logout(): any {
console.log("logout");
// TODO Why isn't the request send??
let t = this.http.post('users/logout', undefined, httpOptions);
/*.pipe(
tap(val => {
console.log(val)
}),
catchError(this.handleError())
);*/
console.log(t);
}
//very generic error handling function.
//implicitly returns an observable that will display an error message
private handleError<T>() {
return (error: any): Observable<T> => {
console.error(error);
return of(error);
}
};
}

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService, WhoAmIResponse } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class OpenSlidesService {
constructor(private auth: AuthService, private router: Router) { }
bootup () {
// TODO Lock the interface..
this.auth.init().subscribe((whoami: WhoAmIResponse) => {
console.log(whoami);
if (!whoami.user && !whoami.guest_enabled) {
this.router.navigate(['/login']);
} else {
// It's ok!
}
});
}
}

View File

@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class RouterAuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return this.checkAccess(state.url);
}
checkAccess(url: string): boolean {
if (url === '/login') {
return true;
}
// Check base permission for the current state
let hasBasePermission = true; // TODO: get this from the current state...
if (!hasBasePermission) {
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
return true;
}
}

View File

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { Title } from '@angular/platform-browser';
// Provides functionallity to set the webpage title
@Injectable({
providedIn: 'root'
})
export class TitleService {
private titleSuffix: string = " - OpenSlides 3";
constructor(protected titleService: Title) { }
setTitle(prefix: string) {
this.titleService.setTitle(prefix + this.titleSuffix);
}
}

View File

@ -1,68 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { BaseComponent } from '../base.component';
import { User } from '../users/user';
import { AuthenticationService } from '../_services/authentication.service';
import { AlertService } from '../_services/alert.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent extends BaseComponent implements OnInit {
user: User = {
username: '',
password: ''
};
info: string;
constructor(
titleService: Title,
private authenticationService: AuthenticationService,
private alertService: AlertService,
private router: Router,
) {
super(titleService);
this.setInfo();
}
ngOnInit() {
//TODO translate
super.setTitle("Anmelden");
}
setInfo() {
this.info = 'Logged in? ' + (this.authenticationService.isLoggedIn ? 'in' : 'out');
}
//Todo: This serves as a prototype and need enhancement,
//like saving a "logged in state" and real checking the server
//if logIn was fine
onSubmit() {
this.authenticationService.loginUser(this.user).subscribe(
res => {
if (res.status === 400) {
//TODO, add more errors here, use translation
this.alertService.error("Benutzername oder Passwort war nicht korrekt.");
} else {
this.alertService.success("Logged in! :)");
this.setInfo();
if (this.authenticationService.isLoggedIn) {
localStorage.setItem("username", res.user.username);
// Get the redirect URL from our auth service
// If no redirect has been set, use the default
let redirect = this.authenticationService.redirectUrl ?
this.authenticationService.redirectUrl : '/';
// Redirect the user
this.router.navigate([redirect]);
}
}
}
);
}
}

View File

@ -1,20 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '../base.component';
import { Title } from '@angular/platform-browser';
import { TitleService } from '../core/title.service';
@Component({
selector: 'app-motions',
templateUrl: './motions.component.html',
styleUrls: ['./motions.component.css']
})
export class MotionsComponent extends BaseComponent implements OnInit {
export class MotionsComponent implements OnInit {
constructor(titleService: Title) {
super(titleService)
constructor(private titleService: TitleService) {
}
ngOnInit() {
super.setTitle("Motions");
this.titleService.setTitle("Motions");
}
}

View File

@ -0,0 +1,4 @@
<p>
projector container works!
Here an iframe with the real-projector is needed
</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectorContainerComponent } from './projector-container.component';
describe('ProjectorContainerComponent', () => {
let component: ProjectorContainerComponent;
let fixture: ComponentFixture<ProjectorContainerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ProjectorContainerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectorContainerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { TitleService } from '../core/title.service';
@Component({
selector: 'app-projector-container',
templateUrl: './projector-container.component.html',
styleUrls: ['./projector-container.component.css']
})
export class ProjectorContainerComponent implements OnInit {
constructor(protected titleService: TitleService) {
}
ngOnInit() {
this.titleService.setTitle('Projector');
}
}

View File

@ -1,20 +1,15 @@
import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '../base.component';
import { Title } from '@angular/platform-browser';
@Component({
selector: 'app-projector',
templateUrl: './projector.component.html',
styleUrls: ['./projector.component.css']
})
export class ProjectorComponent extends BaseComponent implements OnInit {
export class ProjectorComponent implements OnInit {
constructor(protected titleService: Title) {
super(titleService)
constructor() {
}
ngOnInit() {
super.setTitle("Projector");
}
}

View File

@ -0,0 +1,4 @@
<div *ngIf="alert" [ngClass]="cssClass(alert)" class="alert-dismissable">
{{alert.message}}
<a class="close" (click)="removeAlert(alert)">&times;</a>
</div>

View File

@ -0,0 +1,34 @@
import { Component, OnInit, Input } from '@angular/core';
import { Alert, AlertType } from './alert';
@Component({
selector: 'alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.css']
})
export class AlertComponent implements OnInit {
@Input() alert: Alert;
constructor() { }
ngOnInit() { }
removeAlert(alert: Alert) {
this.alert = undefined;
}
cssClass(alert: Alert) {
// return css class based on alert type
switch (alert.type) {
case AlertType.Success:
return 'alert alert-success';
case AlertType.Error:
return 'alert alert-danger';
case AlertType.Info:
return 'alert alert-info';
case AlertType.Warning:
return 'alert alert-warning';
}
}
}

View File

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { Alert, AlertType } from './alert';
@Injectable({
providedIn: 'root'
})
export class AlertService {
constructor() { }
success(message: string): Alert {
return this.alert(AlertType.Success, message);
}
error(message: string): Alert {
return this.alert(AlertType.Error, message);
}
info(message: string): Alert {
return this.alert(AlertType.Info, message);
}
warn(message: string): Alert {
return this.alert(AlertType.Warning, message);
}
alert(type: AlertType, message: string): Alert {
return <Alert>{ type: type, message: message };
}
// TODO fromHttpError() to generate alerts form http errors
}

View File

@ -1,3 +1,4 @@
<h1> The actual OpenSldies Content! <h2>
<h1>The actual OpenSldies Content!</h1>
<nav>A nav will be here</nav>
<router-outlet></router-outlet>
<footer>Footer</footer>

View File

@ -9,7 +9,5 @@ export class SiteComponent implements OnInit {
constructor() { }
ngOnInit() {
}
ngOnInit() { }
}

View File

View File

@ -0,0 +1,7 @@
<p>
start works!
Here the welcome text is displayed
</p>
<button type="button" (click)="logout()" class="btn btn-primary">
Logoff test!
</button>

View File

@ -0,0 +1,22 @@
import { Component, OnInit } from '@angular/core';
import { TitleService } from '../core/title.service';
import {AuthService } from '../core/auth.service';
@Component({
selector: 'app-start',
templateUrl: './start.component.html',
styleUrls: ['./start.component.css']
})
export class StartComponent implements OnInit {
constructor(private titleService: TitleService, private auth: AuthService) { }
ngOnInit() {
this.titleService.setTitle('Start page');
}
logout() {
this.auth.logout();
}
}

View File

@ -1,3 +0,0 @@
<p>
start works!
</p>

View File

@ -1,20 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '../base.component';
import { Title } from '@angular/platform-browser';
@Component({
selector: 'app-start',
templateUrl: './start.component.html',
styleUrls: ['./start.component.css']
})
export class StartComponent extends BaseComponent implements OnInit {
constructor(titleService: Title) {
super(titleService)
}
ngOnInit() {
super.setTitle("Start page");
}
}

View File

@ -4,9 +4,8 @@
<img src="/assets/img/openslides-logo.png" alt="OpenSlides" class="login-logo center-block">
</div>
<div class="modal-body loginForm">
{{info}}
<!-- Instead of the original approach, user alert component -->
<alert></alert>
<alert [alert]="alert"></alert>
<div class="input-group form-group">
<div class="input-group-addon">

View File

@ -0,0 +1,58 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { User } from '../users/user';
import { AuthService } from '../core/auth.service';
import { AlertService } from '../site/alert.service';
import { TitleService } from '../core/title.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
user: User = {
username: '',
password: ''
};
constructor(
private titleService: TitleService,
private authService: AuthService,
private alertService: AlertService,
private router: Router,
) { }
ngOnInit() {
//TODO translate
this.titleService.setTitle('Anmelden');
}
//Todo: This serves as a prototype and need enhancement,
//like saving a "logged in state" and real checking the server
//if logIn was fine
onSubmit() {
this.authService.loginUser(this.user).subscribe(
res => {
// TODO an error is thrown here. Also all this stuff should the the auth service..
/*if (res.status === 400) {
// TODO Use the error that comes from the server
//this.alertService.error("Benutzername oder Passwort war nicht korrekt.");
} else {
if (this.authService.isLoggedIn) {
// Get the redirect URL from our auth service
// If no redirect has been set, use the default
let redirect = this.authService.redirectUrl ?
this.authService.redirectUrl : '/';
// TODO check, if there is a redirect url. Else redirect to /
// Redirect the user
this.router.navigate([redirect]);
}
}*/
}
);
}
}