From 0416e0c24ecbd16cfbe79b272eb7b1145c5bc1b7 Mon Sep 17 00:00:00 2001 From: mschwrdtnr Date: Mon, 2 Sep 2019 20:51:58 +0200 Subject: [PATCH 1/6] init login module --- .vscode/settings.json | 3 +++ .../components/login/login.component.html | 1 + .../components/login/login.component.scss | 0 .../components/login/login.component.spec.ts | 25 +++++++++++++++++++ .../login/components/login/login.component.ts | 15 +++++++++++ .../src/app/src/modules/login/login.module.ts | 13 ++++++++++ 6 files changed, 57 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 devday/src/app/src/modules/login/components/login/login.component.html create mode 100644 devday/src/app/src/modules/login/components/login/login.component.scss create mode 100644 devday/src/app/src/modules/login/components/login/login.component.spec.ts create mode 100644 devday/src/app/src/modules/login/components/login/login.component.ts create mode 100644 devday/src/app/src/modules/login/login.module.ts 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/src/modules/login/components/login/login.component.html b/devday/src/app/src/modules/login/components/login/login.component.html new file mode 100644 index 0000000..147cfc4 --- /dev/null +++ b/devday/src/app/src/modules/login/components/login/login.component.html @@ -0,0 +1 @@ +

login works!

diff --git a/devday/src/app/src/modules/login/components/login/login.component.scss b/devday/src/app/src/modules/login/components/login/login.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/devday/src/app/src/modules/login/components/login/login.component.spec.ts b/devday/src/app/src/modules/login/components/login/login.component.spec.ts new file mode 100644 index 0000000..d6d85a8 --- /dev/null +++ b/devday/src/app/src/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/src/modules/login/components/login/login.component.ts b/devday/src/app/src/modules/login/components/login/login.component.ts new file mode 100644 index 0000000..12de138 --- /dev/null +++ b/devday/src/app/src/modules/login/components/login/login.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/devday/src/app/src/modules/login/login.module.ts b/devday/src/app/src/modules/login/login.module.ts new file mode 100644 index 0000000..6ca0181 --- /dev/null +++ b/devday/src/app/src/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 { } From bb485082c0cc2fd5d6bfcc77103407914242a0d7 Mon Sep 17 00:00:00 2001 From: Soeren Schreier Date: Mon, 2 Sep 2019 21:04:46 +0200 Subject: [PATCH 2/6] Remove src folder --- .../{src => }/modules/login/components/login/login.component.html | 0 .../{src => }/modules/login/components/login/login.component.scss | 0 .../modules/login/components/login/login.component.spec.ts | 0 .../{src => }/modules/login/components/login/login.component.ts | 0 devday/src/app/{src => }/modules/login/login.module.ts | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename devday/src/app/{src => }/modules/login/components/login/login.component.html (100%) rename devday/src/app/{src => }/modules/login/components/login/login.component.scss (100%) rename devday/src/app/{src => }/modules/login/components/login/login.component.spec.ts (100%) rename devday/src/app/{src => }/modules/login/components/login/login.component.ts (100%) rename devday/src/app/{src => }/modules/login/login.module.ts (100%) diff --git a/devday/src/app/src/modules/login/components/login/login.component.html b/devday/src/app/modules/login/components/login/login.component.html similarity index 100% rename from devday/src/app/src/modules/login/components/login/login.component.html rename to devday/src/app/modules/login/components/login/login.component.html diff --git a/devday/src/app/src/modules/login/components/login/login.component.scss b/devday/src/app/modules/login/components/login/login.component.scss similarity index 100% rename from devday/src/app/src/modules/login/components/login/login.component.scss rename to devday/src/app/modules/login/components/login/login.component.scss diff --git a/devday/src/app/src/modules/login/components/login/login.component.spec.ts b/devday/src/app/modules/login/components/login/login.component.spec.ts similarity index 100% rename from devday/src/app/src/modules/login/components/login/login.component.spec.ts rename to devday/src/app/modules/login/components/login/login.component.spec.ts diff --git a/devday/src/app/src/modules/login/components/login/login.component.ts b/devday/src/app/modules/login/components/login/login.component.ts similarity index 100% rename from devday/src/app/src/modules/login/components/login/login.component.ts rename to devday/src/app/modules/login/components/login/login.component.ts diff --git a/devday/src/app/src/modules/login/login.module.ts b/devday/src/app/modules/login/login.module.ts similarity index 100% rename from devday/src/app/src/modules/login/login.module.ts rename to devday/src/app/modules/login/login.module.ts From f35ddf51ce1254bc9077b042e2ce7d92e0f294f0 Mon Sep 17 00:00:00 2001 From: Soeren Schreier Date: Mon, 2 Sep 2019 22:25:20 +0200 Subject: [PATCH 3/6] initial model, service, helper, guards --- devday/src/app/models/user.ts | 8 ++ devday/src/app/routing/auth.guard.spec.ts | 15 ++ devday/src/app/routing/auth.guard.ts | 26 ++++ devday/src/app/services/alert.service.spec.ts | 12 ++ devday/src/app/services/alert.service.ts | 41 ++++++ .../services/authentication.service.spec.ts | 12 ++ .../app/services/authentication.service.ts | 44 ++++++ devday/src/app/services/user.service.spec.ts | 12 ++ devday/src/app/services/user.service.ts | 32 +++++ .../app/shared/helper/error.interceptor.ts | 25 ++++ devday/src/app/shared/helper/fake-backend.ts | 133 ++++++++++++++++++ .../src/app/shared/helper/jwt.interceptor.ts | 25 ++++ devday/src/environments/environment.prod.ts | 3 +- devday/src/environments/environment.ts | 3 +- 14 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 devday/src/app/models/user.ts create mode 100644 devday/src/app/routing/auth.guard.spec.ts create mode 100644 devday/src/app/routing/auth.guard.ts create mode 100644 devday/src/app/services/alert.service.spec.ts create mode 100644 devday/src/app/services/alert.service.ts create mode 100644 devday/src/app/services/authentication.service.spec.ts create mode 100644 devday/src/app/services/authentication.service.ts create mode 100644 devday/src/app/services/user.service.spec.ts create mode 100644 devday/src/app/services/user.service.ts create mode 100644 devday/src/app/shared/helper/error.interceptor.ts create mode 100644 devday/src/app/shared/helper/fake-backend.ts create mode 100644 devday/src/app/shared/helper/jwt.interceptor.ts diff --git a/devday/src/app/models/user.ts b/devday/src/app/models/user.ts new file mode 100644 index 0000000..563bc19 --- /dev/null +++ b/devday/src/app/models/user.ts @@ -0,0 +1,8 @@ +export class User { + id: number; + username: string; + password: string; + firstName: string; + lastName: string; + token: string; +} \ No newline at end of file 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..d0035a4 --- /dev/null +++ b/devday/src/app/services/alert.service.ts @@ -0,0 +1,41 @@ +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 + 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.subject.next(); + } + } + }); + } + + 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 }); + } + + getMessage(): Observable { + return this.subject.asObservable(); + } +} 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..1920d84 --- /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(username: string, password: string) { + return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, 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..a90799c --- /dev/null +++ b/devday/src/app/services/user.service.ts @@ -0,0 +1,32 @@ +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..4c0769c --- /dev/null +++ b/devday/src/app/shared/helper/fake-backend.ts @@ -0,0 +1,133 @@ +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 + let 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 + let filteredUsers = users.filter(user => { + return user.username === request.body.username && user.password === request.body.password; + }); + + if (filteredUsers.length) { + // if login details are valid return 200 OK with user details and fake jwt token + let user = filteredUsers[0]; + let body = { + id: user.id, + username: user.username, + firstName: user.firstName, + lastName: user.lastName, + token: 'fake-jwt-token' + }; + + return of(new HttpResponse({ status: 200, body: body })); + } else { + // else return 400 bad request + return throwError({ error: { message: 'Username or password is incorrect' } }); + } + } + + // 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: 'Unauthorised' } }); + } + } + + // 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 + let urlParts = request.url.split('/'); + let id = parseInt(urlParts[urlParts.length - 1]); + let matchedUsers = users.filter(user => { return user.id === id; }); + let 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: 'Unauthorised' } }); + } + } + + // register user + if (request.url.endsWith('/users/register') && request.method === 'POST') { + // get new user object from post body + let newUser = request.body; + + // validation + let duplicateUser = users.filter(user => { return user.username === newUser.username; }).length; + if (duplicateUser) { + return throwError({ error: { message: 'Username "' + newUser.username + '" is already taken' } }); + } + + // 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 + let urlParts = request.url.split('/'); + let id = parseInt(urlParts[urlParts.length - 1]); + for (let i = 0; i < users.length; i++) { + let 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: 'Unauthorised' } }); + } + } + + // 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 +}; \ No newline at end of file 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' }; /* From 4608fe7993c0fb76c0af835411eab5cf0e9c315d Mon Sep 17 00:00:00 2001 From: mschwrdtnr Date: Mon, 2 Sep 2019 22:32:11 +0200 Subject: [PATCH 4/6] create components --- devday/src/app/app-routing.module.ts | 11 --- devday/src/app/app.component.html | 38 +++++------ devday/src/app/app.component.ts | 23 +++++-- devday/src/app/app.module.ts | 34 ++++++---- devday/src/app/modules/alert/alert.module.ts | 13 ++++ .../components/alert/alert.component.html | 1 + .../components/alert/alert.component.scss | 0 .../components/alert/alert.component.spec.ts | 25 +++++++ .../alert/components/alert/alert.component.ts | 30 +++++++++ .../components/login/login.component.html | 35 +++++++++- .../login/components/login/login.component.ts | 65 ++++++++++++++++-- devday/src/app/pages/home/home.component.html | 9 +++ devday/src/app/pages/home/home.component.scss | 0 .../src/app/pages/home/home.component.spec.ts | 25 +++++++ devday/src/app/pages/home/home.component.ts | 33 +++++++++ .../pages/register/register.component.html | 59 ++++++++++++++++ .../pages/register/register.component.scss | 0 .../pages/register/register.component.spec.ts | 25 +++++++ .../app/pages/register/register.component.ts | 67 +++++++++++++++++++ devday/src/app/routing/app.routing.ts | 17 +++++ devday/src/index.html | 27 ++++---- devday/src/main.ts | 12 +--- devday/src/polyfills.ts | 4 +- 23 files changed, 470 insertions(+), 83 deletions(-) delete mode 100644 devday/src/app/app-routing.module.ts create mode 100644 devday/src/app/modules/alert/alert.module.ts create mode 100644 devday/src/app/modules/alert/components/alert/alert.component.html create mode 100644 devday/src/app/modules/alert/components/alert/alert.component.scss create mode 100644 devday/src/app/modules/alert/components/alert/alert.component.spec.ts create mode 100644 devday/src/app/modules/alert/components/alert/alert.component.ts create mode 100644 devday/src/app/pages/home/home.component.html create mode 100644 devday/src/app/pages/home/home.component.scss create mode 100644 devday/src/app/pages/home/home.component.spec.ts create mode 100644 devday/src/app/pages/home/home.component.ts create mode 100644 devday/src/app/pages/register/register.component.html create mode 100644 devday/src/app/pages/register/register.component.scss create mode 100644 devday/src/app/pages/register/register.component.spec.ts create mode 100644 devday/src/app/pages/register/register.component.ts create mode 100644 devday/src/app/routing/app.routing.ts 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 }}! -

- Angular Logo -
-

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..f0fa293 100644 --- a/devday/src/app/app.component.ts +++ b/devday/src/app/app.component.ts @@ -1,10 +1,21 @@ import { Component } from '@angular/core'; +import { Router } from '@angular/router'; -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] -}) +import { AuthenticationService } from './_services'; +import { User } from './_models'; + +import './_content/app.less'; + +@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..9571e80 100644 --- a/devday/src/app/app.module.ts +++ b/devday/src/app/app.module.ts @@ -1,21 +1,29 @@ -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 './_helpers'; -import { AppRoutingModule } from './app-routing.module'; +import { JwtInterceptor, ErrorInterceptor } from './_helpers'; import { AppComponent } from './app.component'; -import { ServiceWorkerModule } from '@angular/service-worker'; -import { environment } from '../environments/environment'; +import { HomeComponent } from './home'; +import { LoginComponent } from './login'; +import { RegisterComponent } from './register'; +import { AlertComponent } from './_components'; +import { appRoutingModule } from './routing/app.routing'; @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/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..e89108e --- /dev/null +++ b/devday/src/app/modules/alert/components/alert/alert.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { AlertService } from '@/_services'; + +@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 index 147cfc4..e938011 100644 --- a/devday/src/app/modules/login/components/login/login.component.html +++ b/devday/src/app/modules/login/components/login/login.component.html @@ -1 +1,34 @@ -

login works!

+

Login

+
+
+ + +
+
Username is required
+
+
+
+ + +
+
Password is required
+
+
+
+ + Register +
+
diff --git a/devday/src/app/modules/login/components/login/login.component.ts b/devday/src/app/modules/login/components/login/login.component.ts index 12de138..a2347b7 100644 --- a/devday/src/app/modules/login/components/login/login.component.ts +++ b/devday/src/app/modules/login/components/login/login.component.ts @@ -1,15 +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'; -@Component({ - selector: 'app-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.scss'] -}) +import { AlertService, AuthenticationService } from '@/_services'; + +@Component({ templateUrl: 'login.component.html' }) export class LoginComponent implements OnInit { + loginForm: FormGroup; + loading = false; + submitted = false; + returnUrl: string; - constructor() { } + 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({ + username: ['', 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.username.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/pages/home/home.component.html b/devday/src/app/pages/home/home.component.html new file mode 100644 index 0000000..1791240 --- /dev/null +++ b/devday/src/app/pages/home/home.component.html @@ -0,0 +1,9 @@ +

Hi {{ currentUser.firstName }}!

+

You're logged in with Angular 8!!

+

All registered users:

+
    +
  • + {{ user.username }} ({{ user.firstName }} {{ user.lastName }}) - + Delete +
  • +
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..f495d87 --- /dev/null +++ b/devday/src/app/pages/home/home.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { first } from 'rxjs/operators'; + +import { User } from '@/_models'; +import { UserService, AuthenticationService } from '@/_services'; + +@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..0722b5a --- /dev/null +++ b/devday/src/app/pages/register/register.component.html @@ -0,0 +1,59 @@ +

Register

+
+
+ + +
+
First Name is required
+
+
+
+ + +
+
Last Name is required
+
+
+
+ + +
+
Username is required
+
+
+
+ + +
+
Password is required
+
Password must be at least 6 characters
+
+
+
+ + Cancel +
+
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..bf34f2e --- /dev/null +++ b/devday/src/app/pages/register/register.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { first } from 'rxjs/operators'; + +import { AlertService, UserService, AuthenticationService } from '@/_services'; + +@Component({ templateUrl: 'register.component.html' }) +export class RegisterComponent implements OnInit { + registerForm: FormGroup; + loading = false; + submitted = false; + + 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({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + username: ['', Validators.required], + password: ['', [Validators.required, Validators.minLength(6)]] + }); + } + + // 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..674e2c6 --- /dev/null +++ b/devday/src/app/routing/app.routing.ts @@ -0,0 +1,17 @@ +import { Routes, RouterModule } from '@angular/router'; + +import { HomeComponent } from './home'; +import { LoginComponent } from './login'; +import { RegisterComponent } from './register'; +import { AuthGuard } from './_helpers'; + +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/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 From e7d5f1af36848f40cb6a678912f80c8888e5713a Mon Sep 17 00:00:00 2001 From: Soeren Schreier Date: Mon, 2 Sep 2019 23:17:42 +0200 Subject: [PATCH 5/6] Merge code and fix imports --- devday/src/app/app.component.ts | 5 +- devday/src/app/app.module.ts | 19 ++++--- .../alert/components/alert/alert.component.ts | 28 +++++----- .../components/login/login.component.html | 14 ++--- .../login/components/login/login.component.ts | 3 +- devday/src/app/pages/home/home.component.html | 8 +-- devday/src/app/pages/home/home.component.ts | 5 +- .../pages/register/register.component.html | 24 ++++----- .../app/pages/register/register.component.ts | 5 +- devday/src/app/routing/app.routing.ts | 8 +-- devday/src/app/services/alert.service.ts | 44 +++++++++------- .../app/services/authentication.service.ts | 52 +++++++++---------- devday/src/app/shared/helper/fake-backend.ts | 10 ++-- 13 files changed, 122 insertions(+), 103 deletions(-) diff --git a/devday/src/app/app.component.ts b/devday/src/app/app.component.ts index f0fa293..5540d69 100644 --- a/devday/src/app/app.component.ts +++ b/devday/src/app/app.component.ts @@ -1,10 +1,9 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; +import { User } from './models/user'; +import { AuthenticationService } from './services/authentication.service'; -import { AuthenticationService } from './_services'; -import { User } from './_models'; -import './_content/app.less'; @Component({ selector: 'app', templateUrl: 'app.component.html' }) export class AppComponent { diff --git a/devday/src/app/app.module.ts b/devday/src/app/app.module.ts index 9571e80..f9ac4a8 100644 --- a/devday/src/app/app.module.ts +++ b/devday/src/app/app.module.ts @@ -4,16 +4,21 @@ import { ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; // used to create fake backend -import { fakeBackendProvider } from './_helpers'; +import { fakeBackendProvider } from './shared/helper/fake-backend'; + +import { JwtInterceptor } from './shared/helper/jwt.interceptor'; +import { ErrorInterceptor } from './shared/helper/error.interceptor'; -import { JwtInterceptor, ErrorInterceptor } from './_helpers'; -import { AppComponent } from './app.component'; -import { HomeComponent } from './home'; -import { LoginComponent } from './login'; -import { RegisterComponent } from './register'; -import { AlertComponent } from './_components'; import { appRoutingModule } from './routing/app.routing'; +import { AppComponent } from './app.component'; +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({ imports: [BrowserModule, ReactiveFormsModule, HttpClientModule, appRoutingModule], declarations: [AppComponent, HomeComponent, LoginComponent, RegisterComponent, AlertComponent], diff --git a/devday/src/app/modules/alert/components/alert/alert.component.ts b/devday/src/app/modules/alert/components/alert/alert.component.ts index e89108e..1a01bb3 100644 --- a/devday/src/app/modules/alert/components/alert/alert.component.ts +++ b/devday/src/app/modules/alert/components/alert/alert.component.ts @@ -1,27 +1,29 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; -import { AlertService } from '@/_services'; +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) {} + 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.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; - }); + this.message = message; + }); } ngOnDestroy() { diff --git a/devday/src/app/modules/login/components/login/login.component.html b/devday/src/app/modules/login/components/login/login.component.html index e938011..68095e1 100644 --- a/devday/src/app/modules/login/components/login/login.component.html +++ b/devday/src/app/modules/login/components/login/login.component.html @@ -1,7 +1,7 @@ -

Login

+

Anmelden

- + Login [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
-
Username is required
+
Benutzername ist erforderlich
- + Login [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
-
Password is required
+
Passwort ist erforderlich
- Register + Registrieren
diff --git a/devday/src/app/modules/login/components/login/login.component.ts b/devday/src/app/modules/login/components/login/login.component.ts index a2347b7..2768733 100644 --- a/devday/src/app/modules/login/components/login/login.component.ts +++ b/devday/src/app/modules/login/components/login/login.component.ts @@ -2,8 +2,9 @@ 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'; -import { AlertService, AuthenticationService } from '@/_services'; @Component({ templateUrl: 'login.component.html' }) export class LoginComponent implements OnInit { diff --git a/devday/src/app/pages/home/home.component.html b/devday/src/app/pages/home/home.component.html index 1791240..b2d6246 100644 --- a/devday/src/app/pages/home/home.component.html +++ b/devday/src/app/pages/home/home.component.html @@ -1,9 +1,9 @@ -

Hi {{ currentUser.firstName }}!

-

You're logged in with Angular 8!!

-

All registered users:

+

Hallo {{ currentUser.firstName }}!

+

Sie sind eingeloggt

+

Alle registrierten Benutzer:

  • {{ user.username }} ({{ user.firstName }} {{ user.lastName }}) - - Delete + Löschen
diff --git a/devday/src/app/pages/home/home.component.ts b/devday/src/app/pages/home/home.component.ts index f495d87..852ac69 100644 --- a/devday/src/app/pages/home/home.component.ts +++ b/devday/src/app/pages/home/home.component.ts @@ -1,8 +1,9 @@ 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'; -import { User } from '@/_models'; -import { UserService, AuthenticationService } from '@/_services'; @Component({ templateUrl: 'home.component.html' }) export class HomeComponent implements OnInit { diff --git a/devday/src/app/pages/register/register.component.html b/devday/src/app/pages/register/register.component.html index 0722b5a..c0a5c27 100644 --- a/devday/src/app/pages/register/register.component.html +++ b/devday/src/app/pages/register/register.component.html @@ -1,7 +1,7 @@ -

Register

+

Registrieren

- + Register [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
-
First Name is required
+
Vorname ist erforderlich
- + Register [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
-
Last Name is required
+
Nachname is erforderlich
- + Register [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
-
Username is required
+
Benutzername ist erforderlich
- + Register [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
-
Password is required
-
Password must be at least 6 characters
+
Passwort ist erforderlich
+
Passwort muss mindestens 6 Zeichen lang sein
- Cancel + Abbrechen
diff --git a/devday/src/app/pages/register/register.component.ts b/devday/src/app/pages/register/register.component.ts index bf34f2e..7efd679 100644 --- a/devday/src/app/pages/register/register.component.ts +++ b/devday/src/app/pages/register/register.component.ts @@ -2,8 +2,11 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { FormBuilder, FormGroup, Validators } 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'; + -import { AlertService, UserService, AuthenticationService } from '@/_services'; @Component({ templateUrl: 'register.component.html' }) export class RegisterComponent implements OnInit { diff --git a/devday/src/app/routing/app.routing.ts b/devday/src/app/routing/app.routing.ts index 674e2c6..58d1a05 100644 --- a/devday/src/app/routing/app.routing.ts +++ b/devday/src/app/routing/app.routing.ts @@ -1,9 +1,9 @@ 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'; -import { HomeComponent } from './home'; -import { LoginComponent } from './login'; -import { RegisterComponent } from './register'; -import { AuthGuard } from './_helpers'; const routes: Routes = [ { path: '', component: HomeComponent, canActivate: [AuthGuard] }, diff --git a/devday/src/app/services/alert.service.ts b/devday/src/app/services/alert.service.ts index d0035a4..5de9e9c 100644 --- a/devday/src/app/services/alert.service.ts +++ b/devday/src/app/services/alert.service.ts @@ -11,31 +11,39 @@ export class AlertService { private keepAfterNavigationChange = false; constructor(private router: Router) { - // clear alert message on route change - 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.subject.next(); - } - } - }); + // 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 }); + 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 }); + this.keepAfterNavigationChange = keepAfterNavigationChange; + this.subject.next({ type: 'error', text: message }); } - getMessage(): Observable { - return this.subject.asObservable(); + 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.ts b/devday/src/app/services/authentication.service.ts index 1920d84..5c73f3f 100644 --- a/devday/src/app/services/authentication.service.ts +++ b/devday/src/app/services/authentication.service.ts @@ -10,35 +10,35 @@ import { User } from '../models/user'; }) export class AuthenticationService { - private currentUserSubject: BehaviorSubject; - public currentUser: Observable; + private currentUserSubject: BehaviorSubject; + public currentUser: Observable; - constructor(private http: HttpClient) { - this.currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser'))); - this.currentUser = this.currentUserSubject.asObservable(); - } + 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; - } + public get currentUserValue(): User { + return this.currentUserSubject.value; + } - login(username: string, password: string) { - return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, 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); - } + login(username: string, password: string) { + return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, 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; - })); - } + return user; + })); + } - logout() { - // remove user from local storage to log user out - localStorage.removeItem('currentUser'); - this.currentUserSubject.next(null); - } + logout() { + // remove user from local storage to log user out + localStorage.removeItem('currentUser'); + this.currentUserSubject.next(null); + } } diff --git a/devday/src/app/shared/helper/fake-backend.ts b/devday/src/app/shared/helper/fake-backend.ts index 4c0769c..36b34d0 100644 --- a/devday/src/app/shared/helper/fake-backend.ts +++ b/devday/src/app/shared/helper/fake-backend.ts @@ -36,7 +36,7 @@ export class FakeBackendInterceptor implements HttpInterceptor { return of(new HttpResponse({ status: 200, body: body })); } else { // else return 400 bad request - return throwError({ error: { message: 'Username or password is incorrect' } }); + return throwError({ error: { message: 'Benutzername oder Passwort ist nicht korrekt' } }); } } @@ -47,7 +47,7 @@ export class FakeBackendInterceptor implements HttpInterceptor { 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: 'Unauthorised' } }); + return throwError({ status: 401, error: { message: 'Nicht authorisiert' } }); } } @@ -64,7 +64,7 @@ export class FakeBackendInterceptor implements HttpInterceptor { 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: 'Unauthorised' } }); + return throwError({ status: 401, error: { message: 'Nicht authorisiert' } }); } } @@ -76,7 +76,7 @@ export class FakeBackendInterceptor implements HttpInterceptor { // validation let duplicateUser = users.filter(user => { return user.username === newUser.username; }).length; if (duplicateUser) { - return throwError({ error: { message: 'Username "' + newUser.username + '" is already taken' } }); + return throwError({ error: { message: 'Benutzername "' + newUser.username + '" wird bereits benutzt' } }); } // save new user @@ -109,7 +109,7 @@ export class FakeBackendInterceptor implements HttpInterceptor { return of(new HttpResponse({ status: 200 })); } else { // return 401 not authorised if token is null or invalid - return throwError({ status: 401, error: { message: 'Unauthorised' } }); + return throwError({ status: 401, error: { message: 'Nicht authorisiert' } }); } } From 2344c9f851c8b62a340f9a94821e06f781c1f54e Mon Sep 17 00:00:00 2001 From: mschwrdtnr Date: Tue, 3 Sep 2019 03:33:54 +0200 Subject: [PATCH 6/6] rename username into email + remove firstname, name + add passwordconfirm + implement ValidatorFn for matching password Co-Authored-By: NemareLethiere --- devday/src/app/models/user.ts | 12 +- .../components/login/login.component.html | 10 +- .../login/components/login/login.component.ts | 5 +- devday/src/app/pages/home/home.component.html | 4 +- .../pages/register/register.component.html | 50 ++--- .../app/pages/register/register.component.ts | 21 +- .../app/services/authentication.service.ts | 54 ++--- devday/src/app/services/user.service.ts | 13 +- devday/src/app/shared/helper/fake-backend.ts | 201 +++++++++--------- 9 files changed, 187 insertions(+), 183 deletions(-) diff --git a/devday/src/app/models/user.ts b/devday/src/app/models/user.ts index 563bc19..c783594 100644 --- a/devday/src/app/models/user.ts +++ b/devday/src/app/models/user.ts @@ -1,8 +1,6 @@ export class User { - id: number; - username: string; - password: string; - firstName: string; - lastName: string; - token: string; -} \ No newline at end of file + id: number; + email: string; + password: string; + token: string; +} diff --git a/devday/src/app/modules/login/components/login/login.component.html b/devday/src/app/modules/login/components/login/login.component.html index 68095e1..53fe068 100644 --- a/devday/src/app/modules/login/components/login/login.component.html +++ b/devday/src/app/modules/login/components/login/login.component.html @@ -1,15 +1,15 @@

Anmelden

- + -
-
Benutzername ist erforderlich
+
+
E-Mail ist erforderlich
diff --git a/devday/src/app/modules/login/components/login/login.component.ts b/devday/src/app/modules/login/components/login/login.component.ts index 2768733..57b22e9 100644 --- a/devday/src/app/modules/login/components/login/login.component.ts +++ b/devday/src/app/modules/login/components/login/login.component.ts @@ -5,7 +5,6 @@ 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; @@ -28,7 +27,7 @@ export class LoginComponent implements OnInit { ngOnInit() { this.loginForm = this.formBuilder.group({ - username: ['', Validators.required], + email: ['', Validators.required], password: ['', Validators.required] }); @@ -54,7 +53,7 @@ export class LoginComponent implements OnInit { this.loading = true; this.authenticationService - .login(this.f.username.value, this.f.password.value) + .login(this.f.email.value, this.f.password.value) .pipe(first()) .subscribe( data => { diff --git a/devday/src/app/pages/home/home.component.html b/devday/src/app/pages/home/home.component.html index b2d6246..cd7ad67 100644 --- a/devday/src/app/pages/home/home.component.html +++ b/devday/src/app/pages/home/home.component.html @@ -1,9 +1,9 @@ -

Hallo {{ currentUser.firstName }}!

+

Hallo {{ currentUser.email }}!

Sie sind eingeloggt

Alle registrierten Benutzer:

  • - {{ user.username }} ({{ user.firstName }} {{ user.lastName }}) - + {{ user.email }} ({{ user.email }} {{ user.lastName }}) - Löschen
diff --git a/devday/src/app/pages/register/register.component.html b/devday/src/app/pages/register/register.component.html index c0a5c27..7c1a914 100644 --- a/devday/src/app/pages/register/register.component.html +++ b/devday/src/app/pages/register/register.component.html @@ -1,39 +1,16 @@

Registrieren

- + -
-
Vorname ist erforderlich
-
-
-
- - -
-
Nachname is erforderlich
-
-
-
- - -
-
Benutzername ist erforderlich
+
+
E-Mail ist erforderlich
+
Dies ist keine gültige E-Mail Adresse
@@ -49,6 +26,19 @@

Registrieren

Passwort muss mindestens 6 Zeichen lang sein
+
+ + +
+
Passwort ist erforderlich
+
Passwort stimmt nicht überein
+
+