diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..3b66410
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "git.ignoreLimitWarning": true
+}
\ No newline at end of file
diff --git a/devday/src/app/app-routing.module.ts b/devday/src/app/app-routing.module.ts
deleted file mode 100644
index 06c7342..0000000
--- a/devday/src/app/app-routing.module.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { NgModule } from '@angular/core';
-import { Routes, RouterModule } from '@angular/router';
-
-
-const routes: Routes = [];
-
-@NgModule({
- imports: [RouterModule.forRoot(routes)],
- exports: [RouterModule]
-})
-export class AppRoutingModule { }
diff --git a/devday/src/app/app.component.html b/devday/src/app/app.component.html
index 0f3d9d8..f39219c 100644
--- a/devday/src/app/app.component.html
+++ b/devday/src/app/app.component.html
@@ -1,21 +1,19 @@
-
-
-
- Welcome to {{ title }}!
-
-

-
-Here are some links to help you start:
-
+
+
-
+
+
diff --git a/devday/src/app/app.component.ts b/devday/src/app/app.component.ts
index 0032be0..5540d69 100644
--- a/devday/src/app/app.component.ts
+++ b/devday/src/app/app.component.ts
@@ -1,10 +1,20 @@
import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+import { User } from './models/user';
+import { AuthenticationService } from './services/authentication.service';
-@Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrls: ['./app.component.scss']
-})
+
+
+@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent {
- title = 'devday';
+ currentUser: User;
+
+ constructor(private router: Router, private authenticationService: AuthenticationService) {
+ this.authenticationService.currentUser.subscribe(x => (this.currentUser = x));
+ }
+
+ logout() {
+ this.authenticationService.logout();
+ this.router.navigate(['/login']);
+ }
}
diff --git a/devday/src/app/app.module.ts b/devday/src/app/app.module.ts
index a6a8237..f9ac4a8 100644
--- a/devday/src/app/app.module.ts
+++ b/devday/src/app/app.module.ts
@@ -1,21 +1,34 @@
-import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { ReactiveFormsModule } from '@angular/forms';
+import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
+
+// used to create fake backend
+import { fakeBackendProvider } from './shared/helper/fake-backend';
+
+import { JwtInterceptor } from './shared/helper/jwt.interceptor';
+import { ErrorInterceptor } from './shared/helper/error.interceptor';
+
+import { appRoutingModule } from './routing/app.routing';
-import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
-import { ServiceWorkerModule } from '@angular/service-worker';
-import { environment } from '../environments/environment';
+import { HomeComponent } from './pages/home/home.component';
+import { LoginComponent } from './modules/login/components/login/login.component';
+import { RegisterComponent } from './pages/register/register.component';
+import { AlertComponent } from './modules/alert/components/alert/alert.component';
+
+
@NgModule({
- declarations: [
- AppComponent
- ],
- imports: [
- BrowserModule,
- AppRoutingModule,
- ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
+ imports: [BrowserModule, ReactiveFormsModule, HttpClientModule, appRoutingModule],
+ declarations: [AppComponent, HomeComponent, LoginComponent, RegisterComponent, AlertComponent],
+ providers: [
+ { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
+ { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
+
+ // provider used to create fake backend
+ fakeBackendProvider
],
- providers: [],
bootstrap: [AppComponent]
})
-export class AppModule { }
+export class AppModule {}
diff --git a/devday/src/app/models/user.ts b/devday/src/app/models/user.ts
new file mode 100644
index 0000000..c783594
--- /dev/null
+++ b/devday/src/app/models/user.ts
@@ -0,0 +1,6 @@
+export class User {
+ id: number;
+ email: string;
+ password: string;
+ token: string;
+}
diff --git a/devday/src/app/modules/alert/alert.module.ts b/devday/src/app/modules/alert/alert.module.ts
new file mode 100644
index 0000000..488902f
--- /dev/null
+++ b/devday/src/app/modules/alert/alert.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { AlertComponent } from './components/alert/alert.component';
+
+
+
+@NgModule({
+ declarations: [AlertComponent],
+ imports: [
+ CommonModule
+ ]
+})
+export class AlertModule { }
diff --git a/devday/src/app/modules/alert/components/alert/alert.component.html b/devday/src/app/modules/alert/components/alert/alert.component.html
new file mode 100644
index 0000000..f2f369d
--- /dev/null
+++ b/devday/src/app/modules/alert/components/alert/alert.component.html
@@ -0,0 +1 @@
+{{ message.text }}
diff --git a/devday/src/app/modules/alert/components/alert/alert.component.scss b/devday/src/app/modules/alert/components/alert/alert.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/devday/src/app/modules/alert/components/alert/alert.component.spec.ts b/devday/src/app/modules/alert/components/alert/alert.component.spec.ts
new file mode 100644
index 0000000..3921dd6
--- /dev/null
+++ b/devday/src/app/modules/alert/components/alert/alert.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AlertComponent } from './alert.component';
+
+describe('AlertComponent', () => {
+ let component: AlertComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ AlertComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AlertComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/devday/src/app/modules/alert/components/alert/alert.component.ts b/devday/src/app/modules/alert/components/alert/alert.component.ts
new file mode 100644
index 0000000..1a01bb3
--- /dev/null
+++ b/devday/src/app/modules/alert/components/alert/alert.component.ts
@@ -0,0 +1,32 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { AlertService } from 'src/app/services/alert.service';
+
+
+@Component({ selector: 'alert', templateUrl: 'alert.component.html' })
+export class AlertComponent implements OnInit, OnDestroy {
+ private subscription: Subscription;
+ message: any;
+
+ constructor(private alertService: AlertService) { }
+
+ ngOnInit() {
+ this.subscription = this.alertService.getAlert()
+ .subscribe(message => {
+ switch (message && message.type) {
+ case 'success':
+ message.cssClass = 'alert alert-success';
+ break;
+ case 'error':
+ message.cssClass = 'alert alert-danger';
+ break;
+ }
+
+ this.message = message;
+ });
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+}
diff --git a/devday/src/app/modules/login/components/login/login.component.html b/devday/src/app/modules/login/components/login/login.component.html
new file mode 100644
index 0000000..53fe068
--- /dev/null
+++ b/devday/src/app/modules/login/components/login/login.component.html
@@ -0,0 +1,34 @@
+Anmelden
+
diff --git a/devday/src/app/modules/login/components/login/login.component.scss b/devday/src/app/modules/login/components/login/login.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/devday/src/app/modules/login/components/login/login.component.spec.ts b/devday/src/app/modules/login/components/login/login.component.spec.ts
new file mode 100644
index 0000000..d6d85a8
--- /dev/null
+++ b/devday/src/app/modules/login/components/login/login.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+ let component: LoginComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ LoginComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoginComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/devday/src/app/modules/login/components/login/login.component.ts b/devday/src/app/modules/login/components/login/login.component.ts
new file mode 100644
index 0000000..57b22e9
--- /dev/null
+++ b/devday/src/app/modules/login/components/login/login.component.ts
@@ -0,0 +1,68 @@
+import { Component, OnInit } from '@angular/core';
+import { Router, ActivatedRoute } from '@angular/router';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { first } from 'rxjs/operators';
+import { AuthenticationService } from 'src/app/services/authentication.service';
+import { AlertService } from 'src/app/services/alert.service';
+
+@Component({ templateUrl: 'login.component.html' })
+export class LoginComponent implements OnInit {
+ loginForm: FormGroup;
+ loading = false;
+ submitted = false;
+ returnUrl: string;
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private route: ActivatedRoute,
+ private router: Router,
+ private authenticationService: AuthenticationService,
+ private alertService: AlertService
+ ) {
+ // redirect to home if already logged in
+ if (this.authenticationService.currentUserValue) {
+ this.router.navigate(['/']);
+ }
+ }
+
+ ngOnInit() {
+ this.loginForm = this.formBuilder.group({
+ email: ['', Validators.required],
+ password: ['', Validators.required]
+ });
+
+ // get return url from route parameters or default to '/'
+ this.returnUrl = this.route.snapshot.queryParams.returnUrl || '/';
+ }
+
+ // convenience getter for easy access to form fields
+ get f() {
+ return this.loginForm.controls;
+ }
+
+ onSubmit() {
+ this.submitted = true;
+
+ // reset alerts on submit
+ this.alertService.clear();
+
+ // stop here if form is invalid
+ if (this.loginForm.invalid) {
+ return;
+ }
+
+ this.loading = true;
+ this.authenticationService
+ .login(this.f.email.value, this.f.password.value)
+ .pipe(first())
+ .subscribe(
+ data => {
+ this.router.navigate([this.returnUrl]);
+ },
+ error => {
+ this.alertService.error(error);
+ this.loading = false;
+ }
+ );
+ }
+}
diff --git a/devday/src/app/modules/login/login.module.ts b/devday/src/app/modules/login/login.module.ts
new file mode 100644
index 0000000..6ca0181
--- /dev/null
+++ b/devday/src/app/modules/login/login.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { LoginComponent } from './components/login/login.component';
+
+
+
+@NgModule({
+ declarations: [LoginComponent],
+ imports: [
+ CommonModule
+ ]
+})
+export class LoginModule { }
diff --git a/devday/src/app/pages/home/home.component.html b/devday/src/app/pages/home/home.component.html
new file mode 100644
index 0000000..cd7ad67
--- /dev/null
+++ b/devday/src/app/pages/home/home.component.html
@@ -0,0 +1,9 @@
+Hallo {{ currentUser.email }}!
+Sie sind eingeloggt
+Alle registrierten Benutzer:
+
+ -
+ {{ user.email }} ({{ user.email }} {{ user.lastName }}) -
+ Löschen
+
+
diff --git a/devday/src/app/pages/home/home.component.scss b/devday/src/app/pages/home/home.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/devday/src/app/pages/home/home.component.spec.ts b/devday/src/app/pages/home/home.component.spec.ts
new file mode 100644
index 0000000..490e81b
--- /dev/null
+++ b/devday/src/app/pages/home/home.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HomeComponent } from './home.component';
+
+describe('HomeComponent', () => {
+ let component: HomeComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ HomeComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HomeComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/devday/src/app/pages/home/home.component.ts b/devday/src/app/pages/home/home.component.ts
new file mode 100644
index 0000000..852ac69
--- /dev/null
+++ b/devday/src/app/pages/home/home.component.ts
@@ -0,0 +1,34 @@
+import { Component, OnInit } from '@angular/core';
+import { first } from 'rxjs/operators';
+import { AuthenticationService } from 'src/app/services/authentication.service';
+import { User } from 'src/app/models/user';
+import { UserService } from 'src/app/services/user.service';
+
+
+@Component({ templateUrl: 'home.component.html' })
+export class HomeComponent implements OnInit {
+ currentUser: User;
+ users = [];
+
+ constructor(private authenticationService: AuthenticationService, private userService: UserService) {
+ this.currentUser = this.authenticationService.currentUserValue;
+ }
+
+ ngOnInit() {
+ this.loadAllUsers();
+ }
+
+ deleteUser(id: number) {
+ this.userService
+ .delete(id)
+ .pipe(first())
+ .subscribe(() => this.loadAllUsers());
+ }
+
+ private loadAllUsers() {
+ this.userService
+ .getAll()
+ .pipe(first())
+ .subscribe(users => (this.users = users));
+ }
+}
diff --git a/devday/src/app/pages/register/register.component.html b/devday/src/app/pages/register/register.component.html
new file mode 100644
index 0000000..7c1a914
--- /dev/null
+++ b/devday/src/app/pages/register/register.component.html
@@ -0,0 +1,49 @@
+Registrieren
+
diff --git a/devday/src/app/pages/register/register.component.scss b/devday/src/app/pages/register/register.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/devday/src/app/pages/register/register.component.spec.ts b/devday/src/app/pages/register/register.component.spec.ts
new file mode 100644
index 0000000..6c19551
--- /dev/null
+++ b/devday/src/app/pages/register/register.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RegisterComponent } from './register.component';
+
+describe('RegisterComponent', () => {
+ let component: RegisterComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ RegisterComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RegisterComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/devday/src/app/pages/register/register.component.ts b/devday/src/app/pages/register/register.component.ts
new file mode 100644
index 0000000..4de4d99
--- /dev/null
+++ b/devday/src/app/pages/register/register.component.ts
@@ -0,0 +1,77 @@
+import { Component, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+import { FormBuilder, FormGroup, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
+import { first } from 'rxjs/operators';
+import { AuthenticationService } from 'src/app/services/authentication.service';
+import { UserService } from 'src/app/services/user.service';
+import { AlertService } from 'src/app/services/alert.service';
+
+@Component({ templateUrl: 'register.component.html' })
+export class RegisterComponent implements OnInit {
+ registerForm: FormGroup;
+ loading = false;
+ submitted = false;
+
+ matchPassword(): ValidatorFn {
+ return (currentControl: AbstractControl): {} => {
+ if (this.registerForm && currentControl.value === this.registerForm.controls.password.value) {
+ return null;
+ } else {
+ return { matchPassword: 'noMatch' };
+ }
+ };
+ }
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private router: Router,
+ private authenticationService: AuthenticationService,
+ private userService: UserService,
+ private alertService: AlertService
+ ) {
+ // redirect to home if already logged in
+ if (this.authenticationService.currentUserValue) {
+ this.router.navigate(['/']);
+ }
+ }
+
+ ngOnInit() {
+ this.registerForm = this.formBuilder.group({
+ email: ['', [Validators.required, Validators.email]],
+ password: ['', [Validators.required, Validators.minLength(6)]],
+ password_confirm: ['', [Validators.required, this.matchPassword()]]
+ });
+ }
+
+ // convenience getter for easy access to form fields
+ get f() {
+ return this.registerForm.controls;
+ }
+
+ onSubmit() {
+ this.submitted = true;
+
+ // reset alerts on submit
+ this.alertService.clear();
+
+ // stop here if form is invalid
+ if (this.registerForm.invalid) {
+ return;
+ }
+
+ this.loading = true;
+ this.userService
+ .register(this.registerForm.value)
+ .pipe(first())
+ .subscribe(
+ data => {
+ this.alertService.success('Registration successful', true);
+ this.router.navigate(['/login']);
+ },
+ error => {
+ this.alertService.error(error);
+ this.loading = false;
+ }
+ );
+ }
+}
diff --git a/devday/src/app/routing/app.routing.ts b/devday/src/app/routing/app.routing.ts
new file mode 100644
index 0000000..58d1a05
--- /dev/null
+++ b/devday/src/app/routing/app.routing.ts
@@ -0,0 +1,17 @@
+import { Routes, RouterModule } from '@angular/router';
+import { HomeComponent } from '../pages/home/home.component';
+import { AuthGuard } from './auth.guard';
+import { LoginComponent } from '../modules/login/components/login/login.component';
+import { RegisterComponent } from '../pages/register/register.component';
+
+
+const routes: Routes = [
+ { path: '', component: HomeComponent, canActivate: [AuthGuard] },
+ { path: 'login', component: LoginComponent },
+ { path: 'register', component: RegisterComponent },
+
+ // otherwise redirect to home
+ { path: '**', redirectTo: '' }
+];
+
+export const appRoutingModule = RouterModule.forRoot(routes);
diff --git a/devday/src/app/routing/auth.guard.spec.ts b/devday/src/app/routing/auth.guard.spec.ts
new file mode 100644
index 0000000..7ed05ee
--- /dev/null
+++ b/devday/src/app/routing/auth.guard.spec.ts
@@ -0,0 +1,15 @@
+import { TestBed, async, inject } from '@angular/core/testing';
+
+import { AuthGuard } from './auth.guard';
+
+describe('AuthGuard', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [AuthGuard]
+ });
+ });
+
+ it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
+ expect(guard).toBeTruthy();
+ }));
+});
diff --git a/devday/src/app/routing/auth.guard.ts b/devday/src/app/routing/auth.guard.ts
new file mode 100644
index 0000000..be8bee5
--- /dev/null
+++ b/devday/src/app/routing/auth.guard.ts
@@ -0,0 +1,26 @@
+import { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanActivate, Router } from '@angular/router';
+import { Observable } from 'rxjs';
+import { AuthenticationService } from '../services/authentication.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthGuard implements CanActivate {
+ constructor(
+ private router: Router,
+ private authenticationService: AuthenticationService
+) {}
+
+canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+ const currentUser = this.authenticationService.currentUserValue;
+ if (currentUser) {
+ // authorised so return true
+ return true;
+ }
+
+ // not logged in so redirect to login page with the return url
+ this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
+ return false;
+}
+}
diff --git a/devday/src/app/services/alert.service.spec.ts b/devday/src/app/services/alert.service.spec.ts
new file mode 100644
index 0000000..679f77a
--- /dev/null
+++ b/devday/src/app/services/alert.service.spec.ts
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { AlertService } from './alert.service';
+
+describe('AlertService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: AlertService = TestBed.get(AlertService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/devday/src/app/services/alert.service.ts b/devday/src/app/services/alert.service.ts
new file mode 100644
index 0000000..5de9e9c
--- /dev/null
+++ b/devday/src/app/services/alert.service.ts
@@ -0,0 +1,49 @@
+import { Injectable } from '@angular/core';
+import { Subject, Observable } from 'rxjs';
+import { Router, NavigationStart } from '@angular/router';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AlertService {
+
+ private subject = new Subject();
+ private keepAfterNavigationChange = false;
+
+ constructor(private router: Router) {
+ // clear alert message on route change
+ this.router.events.subscribe(event => {
+ if (event instanceof NavigationStart) {
+ if (this.keepAfterNavigationChange) {
+ // only keep for a single location change
+ this.keepAfterNavigationChange = false;
+ } else {
+ // clear alert
+ this.clear();
+ }
+ }
+ });
+ }
+
+ success(message: string, keepAfterNavigationChange = false) {
+ this.keepAfterNavigationChange = keepAfterNavigationChange;
+ this.subject.next({ type: 'success', text: message });
+ }
+
+ error(message: string, keepAfterNavigationChange = false) {
+ this.keepAfterNavigationChange = keepAfterNavigationChange;
+ this.subject.next({ type: 'error', text: message });
+ }
+
+ getAlert(): Observable {
+ return this.subject.asObservable();
+ }
+
+ clear() {
+
+ // clear by calling subject.next() without parameters
+
+ this.subject.next();
+
+ }
+}
diff --git a/devday/src/app/services/authentication.service.spec.ts b/devday/src/app/services/authentication.service.spec.ts
new file mode 100644
index 0000000..91a1e97
--- /dev/null
+++ b/devday/src/app/services/authentication.service.spec.ts
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { AuthenticationService } from './authentication.service';
+
+describe('AuthenticationService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: AuthenticationService = TestBed.get(AuthenticationService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/devday/src/app/services/authentication.service.ts b/devday/src/app/services/authentication.service.ts
new file mode 100644
index 0000000..fe5e088
--- /dev/null
+++ b/devday/src/app/services/authentication.service.ts
@@ -0,0 +1,44 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { environment } from 'src/environments/environment';
+import { User } from '../models/user';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthenticationService {
+ private currentUserSubject: BehaviorSubject;
+ public currentUser: Observable;
+
+ constructor(private http: HttpClient) {
+ this.currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser')));
+ this.currentUser = this.currentUserSubject.asObservable();
+ }
+
+ public get currentUserValue(): User {
+ return this.currentUserSubject.value;
+ }
+
+ login(email: string, password: string) {
+ return this.http.post(`${environment.apiUrl}/users/authenticate`, { email, password }).pipe(
+ map(user => {
+ // login successful if there's a jwt token in the response
+ if (user && user.token) {
+ // store user details and jwt token in local storage to keep user logged in between page refreshes
+ localStorage.setItem('currentUser', JSON.stringify(user));
+ this.currentUserSubject.next(user);
+ }
+
+ return user;
+ })
+ );
+ }
+
+ logout() {
+ // remove user from local storage to log user out
+ localStorage.removeItem('currentUser');
+ this.currentUserSubject.next(null);
+ }
+}
diff --git a/devday/src/app/services/user.service.spec.ts b/devday/src/app/services/user.service.spec.ts
new file mode 100644
index 0000000..9e7fd1c
--- /dev/null
+++ b/devday/src/app/services/user.service.spec.ts
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { UserService } from './user.service';
+
+describe('UserService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: UserService = TestBed.get(UserService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/devday/src/app/services/user.service.ts b/devday/src/app/services/user.service.ts
new file mode 100644
index 0000000..4c44bf3
--- /dev/null
+++ b/devday/src/app/services/user.service.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { environment } from 'src/environments/environment';
+import { User } from '../models/user';
+import { HttpClient } from '@angular/common/http';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UserService {
+ constructor(private http: HttpClient) {}
+
+ getAll() {
+ return this.http.get(`${environment.apiUrl}/users`);
+ }
+
+ getById(id: number) {
+ return this.http.get(`${environment.apiUrl}/users/${id}`);
+ }
+
+ register(user: User) {
+ return this.http.post(`${environment.apiUrl}/users/register`, user);
+ }
+
+ update(user: User) {
+ return this.http.put(`${environment.apiUrl}/users/${user.id}`, user);
+ }
+
+ delete(id: number) {
+ return this.http.delete(`${environment.apiUrl}/users/${id}`);
+ }
+}
diff --git a/devday/src/app/shared/helper/error.interceptor.ts b/devday/src/app/shared/helper/error.interceptor.ts
new file mode 100644
index 0000000..536faed
--- /dev/null
+++ b/devday/src/app/shared/helper/error.interceptor.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
+import { Observable, throwError } from 'rxjs';
+import { catchError } from 'rxjs/operators';
+import { AuthenticationService } from 'src/app/services/authentication.service';
+
+
+
+@Injectable()
+export class ErrorInterceptor implements HttpInterceptor {
+ constructor(private authenticationService: AuthenticationService) {}
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ return next.handle(request).pipe(catchError(err => {
+ if (err.status === 401) {
+ // auto logout if 401 response returned from api
+ this.authenticationService.logout();
+ location.reload(true);
+ }
+
+ const error = err.error.message || err.statusText;
+ return throwError(error);
+ }))
+ }
+}
\ No newline at end of file
diff --git a/devday/src/app/shared/helper/fake-backend.ts b/devday/src/app/shared/helper/fake-backend.ts
new file mode 100644
index 0000000..1844778
--- /dev/null
+++ b/devday/src/app/shared/helper/fake-backend.ts
@@ -0,0 +1,144 @@
+import { Injectable } from '@angular/core';
+import {
+ HttpRequest,
+ HttpResponse,
+ HttpHandler,
+ HttpEvent,
+ HttpInterceptor,
+ HTTP_INTERCEPTORS
+} from '@angular/common/http';
+import { Observable, of, throwError } from 'rxjs';
+import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
+
+@Injectable()
+export class FakeBackendInterceptor implements HttpInterceptor {
+ constructor() {}
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ // array in local storage for registered users
+ const users: any[] = JSON.parse(localStorage.getItem('users')) || [];
+
+ // wrap in delayed observable to simulate server api call
+ return (
+ of(null)
+ .pipe(
+ mergeMap(() => {
+ // authenticate
+ if (request.url.endsWith('/users/authenticate') && request.method === 'POST') {
+ // find if any user matches login credentials
+ const filteredUsers = users.filter(user => {
+ return user.email === request.body.email && user.password === request.body.password;
+ });
+
+ if (filteredUsers.length) {
+ // if login details are valid return 200 OK with user details and fake jwt token
+ const user = filteredUsers[0];
+ const body = {
+ id: user.id,
+ email: user.email,
+ token: 'fake-jwt-token'
+ };
+
+ return of(new HttpResponse({ status: 200, body }));
+ } else {
+ // else return 400 bad request
+ return throwError({ error: { message: 'E-Mail oder Passwort ist nicht korrekt' } });
+ }
+ }
+
+ // get users
+ if (request.url.endsWith('/users') && request.method === 'GET') {
+ // check for fake auth token in header and return users if valid, this security is implemented server side in a real application
+ if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') {
+ return of(new HttpResponse({ status: 200, body: users }));
+ } else {
+ // return 401 not authorised if token is null or invalid
+ return throwError({ status: 401, error: { message: 'Nicht authorisiert' } });
+ }
+ }
+
+ // get user by id
+ if (request.url.match(/\/users\/\d+$/) && request.method === 'GET') {
+ // check for fake auth token in header and return user if valid, this security is implemented server side in a real application
+ if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') {
+ // find user by id in users array
+ const urlParts = request.url.split('/');
+ const id = parseInt(urlParts[urlParts.length - 1]);
+ const matchedUsers = users.filter(user => {
+ return user.id === id;
+ });
+ const user = matchedUsers.length ? matchedUsers[0] : null;
+
+ return of(new HttpResponse({ status: 200, body: user }));
+ } else {
+ // return 401 not authorised if token is null or invalid
+ return throwError({ status: 401, error: { message: 'Nicht authorisiert' } });
+ }
+ }
+
+ // register user
+ if (request.url.endsWith('/users/register') && request.method === 'POST') {
+ // get new user object from post body
+ const newUser = request.body;
+
+ // validation
+ const duplicateUser = users.filter(user => {
+ return user.email === newUser.email;
+ }).length;
+ if (duplicateUser) {
+ return throwError({ error: { message: 'E-Mail "' + newUser.email + '" wird bereits benutzt' } });
+ }
+
+ // save new user
+ newUser.id = users.length + 1;
+ users.push(newUser);
+ localStorage.setItem('users', JSON.stringify(users));
+
+ // respond 200 OK
+ return of(new HttpResponse({ status: 200 }));
+ }
+
+ // delete user
+ if (request.url.match(/\/users\/\d+$/) && request.method === 'DELETE') {
+ // check for fake auth token in header and return user if valid, this security is implemented server side in a real application
+ if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') {
+ // find user by id in users array
+ const urlParts = request.url.split('/');
+ const id = parseInt(urlParts[urlParts.length - 1]);
+ for (let i = 0; i < users.length; i++) {
+ const user = users[i];
+ if (user.id === id) {
+ // delete user
+ users.splice(i, 1);
+ localStorage.setItem('users', JSON.stringify(users));
+ break;
+ }
+ }
+
+ // respond 200 OK
+ return of(new HttpResponse({ status: 200 }));
+ } else {
+ // return 401 not authorised if token is null or invalid
+ return throwError({ status: 401, error: { message: 'Nicht authorisiert' } });
+ }
+ }
+
+ // pass through any requests not handled above
+ return next.handle(request);
+ })
+ )
+
+ // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
+ .pipe(materialize())
+ .pipe(delay(500))
+ .pipe(dematerialize())
+ );
+ }
+}
+
+export let fakeBackendProvider = {
+ // use fake backend in place of Http service for backend-less development
+ provide: HTTP_INTERCEPTORS,
+ useClass: FakeBackendInterceptor,
+ multi: true
+};
diff --git a/devday/src/app/shared/helper/jwt.interceptor.ts b/devday/src/app/shared/helper/jwt.interceptor.ts
new file mode 100644
index 0000000..2339978
--- /dev/null
+++ b/devday/src/app/shared/helper/jwt.interceptor.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { AuthenticationService } from 'src/app/services/authentication.service';
+
+
+
+@Injectable()
+export class JwtInterceptor implements HttpInterceptor {
+ constructor(private authenticationService: AuthenticationService) {}
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ // add authorization header with jwt token if available
+ let currentUser = this.authenticationService.currentUserValue;
+ if (currentUser && currentUser.token) {
+ request = request.clone({
+ setHeaders: {
+ Authorization: `Bearer ${currentUser.token}`
+ }
+ });
+ }
+
+ return next.handle(request);
+ }
+}
\ No newline at end of file
diff --git a/devday/src/environments/environment.prod.ts b/devday/src/environments/environment.prod.ts
index 3612073..ef9a6bb 100644
--- a/devday/src/environments/environment.prod.ts
+++ b/devday/src/environments/environment.prod.ts
@@ -1,3 +1,4 @@
export const environment = {
- production: true
+ production: true,
+ apiUrl: 'http://localhost:4000'
};
diff --git a/devday/src/environments/environment.ts b/devday/src/environments/environment.ts
index 7b4f817..c77b96d 100644
--- a/devday/src/environments/environment.ts
+++ b/devday/src/environments/environment.ts
@@ -3,7 +3,8 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
- production: false
+ production: false,
+ apiUrl: 'http://localhost:4000'
};
/*
diff --git a/devday/src/index.html b/devday/src/index.html
index 09cc40f..478d5f5 100644
--- a/devday/src/index.html
+++ b/devday/src/index.html
@@ -1,17 +1,14 @@
-
-
-
-
- Devday
-
+
+
+
+
+ Angular 8 User Registration and Login Example
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Loading...
+
diff --git a/devday/src/main.ts b/devday/src/main.ts
index c7b673c..1d91a20 100644
--- a/devday/src/main.ts
+++ b/devday/src/main.ts
@@ -1,12 +1,6 @@
-import { enableProdMode } from '@angular/core';
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import './polyfills';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
-import { environment } from './environments/environment';
-
-if (environment.production) {
- enableProdMode();
-}
-platformBrowserDynamic().bootstrapModule(AppModule)
- .catch(err => console.error(err));
+platformBrowserDynamic().bootstrapModule(AppModule);
diff --git a/devday/src/polyfills.ts b/devday/src/polyfills.ts
index aa665d6..346394a 100644
--- a/devday/src/polyfills.ts
+++ b/devday/src/polyfills.ts
@@ -55,8 +55,8 @@
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
-import 'zone.js/dist/zone'; // Included with Angular CLI.
-
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+import 'core-js/features/reflect';
/***************************************************************************************************
* APPLICATION IMPORTS