From 9ab6d35267140e4adfc14d588d0c8f80a2d336c3 Mon Sep 17 00:00:00 2001 From: Takiguchi Date: Wed, 30 Jan 2019 21:43:57 +0100 Subject: [PATCH] Add account settings, change password and profil edition components and auth gard and unauthorized interceptor. --- .../account-settings.component.html | 55 +++++++++++ .../account-settings.component.ts | 24 +++++ .../change-password.component.html | 61 ++++++++++++ .../change-password.component.ts | 36 +++++++ .../change-password.service.ts | 15 +++ .../profil-edition.component.html | 60 +++++++++++ .../profil-edition.component.ts | 99 +++++++++++++++++++ .../profil-edition/profil-edition.service.ts | 26 +++++ src/main/ts-v7/src/app/app.module.ts | 22 ++++- src/main/ts-v7/src/app/app.routing.ts | 8 +- src/main/ts-v7/src/app/core/entities.ts | 2 +- .../ts-v7/src/app/core/guards/auth.guard.ts | 26 +++++ .../interceptors/unauthorized.interceptor.ts | 26 +++++ 13 files changed, 455 insertions(+), 5 deletions(-) create mode 100644 src/main/ts-v7/src/app/account-settings/account-settings.component.html create mode 100644 src/main/ts-v7/src/app/account-settings/account-settings.component.ts create mode 100644 src/main/ts-v7/src/app/account-settings/change-password/change-password.component.html create mode 100644 src/main/ts-v7/src/app/account-settings/change-password/change-password.component.ts create mode 100644 src/main/ts-v7/src/app/account-settings/change-password/change-password.service.ts create mode 100644 src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.html create mode 100644 src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.ts create mode 100644 src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.service.ts create mode 100644 src/main/ts-v7/src/app/core/guards/auth.guard.ts create mode 100644 src/main/ts-v7/src/app/core/interceptors/unauthorized.interceptor.ts diff --git a/src/main/ts-v7/src/app/account-settings/account-settings.component.html b/src/main/ts-v7/src/app/account-settings/account-settings.component.html new file mode 100644 index 0000000..00bc730 --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/account-settings.component.html @@ -0,0 +1,55 @@ +

Paramètres

+
+ + +
+
+
+

+ + Paramètres de compte +

+
+
+ +
+
+
+ Paramètres divers +
+
+
diff --git a/src/main/ts-v7/src/app/account-settings/account-settings.component.ts b/src/main/ts-v7/src/app/account-settings/account-settings.component.ts new file mode 100644 index 0000000..50e20bb --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/account-settings.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; + + +@Component({ + selector: 'app-account-settings', + templateUrl: './account-settings.component.html', + styles: [` + h4 i { + margin-right: 10px; + } + a, a:visited, a:hover { + color: black; + } + .card { + margin-right: 20px; + width: 30%; + display: inline-block; + vertical-align: top; + } + `] +}) +export class AccountSettingsComponent { + +} diff --git a/src/main/ts-v7/src/app/account-settings/change-password/change-password.component.html b/src/main/ts-v7/src/app/account-settings/change-password/change-password.component.html new file mode 100644 index 0000000..5e2c09b --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/change-password/change-password.component.html @@ -0,0 +1,61 @@ +
+
+

Mot de passe

+
+
+ + +
+
+ + +
+
+ + +
+
+
+

{{error}}

+
+
+
+ + Annuler + + +
+
+
+
+ \ No newline at end of file diff --git a/src/main/ts-v7/src/app/account-settings/change-password/change-password.component.ts b/src/main/ts-v7/src/app/account-settings/change-password/change-password.component.ts new file mode 100644 index 0000000..8e05bb6 --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/change-password/change-password.component.ts @@ -0,0 +1,36 @@ +import { Component } from '@angular/core'; +import { PasswordWrapper } from '../../core/entities'; +import { ChangePasswordService } from './change-password.service'; + +@Component({ + selector: 'app-change-password', + templateUrl: './change-password.component.html', + styles: [` + #form.card-body { + padding-bottom: 10px; + } + + .submitFormArea { + line-height: 50px; + } + `] +}) +export class ChangePasswordComponent { + model: PasswordWrapper = new PasswordWrapper('', '', ''); + + error: string; + + constructor( + private changePasswordService: ChangePasswordService + ) {} + + onSubmit(): void { + if (this.model.newPassword !== this.model.confirmPassword) { + this.error = 'Les mots de passe saisis ne correspondent pas.'; + } else { + this.changePasswordService.changePassword(this.model).subscribe(null, error => { + this.error = 'Le mot de passe saisi ne correspond pas au votre.'; + }); + } + } +} diff --git a/src/main/ts-v7/src/app/account-settings/change-password/change-password.service.ts b/src/main/ts-v7/src/app/account-settings/change-password/change-password.service.ts new file mode 100644 index 0000000..c13a6c0 --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/change-password/change-password.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { PasswordWrapper } from '../../core/entities'; + + +@Injectable() +export class ChangePasswordService { + + constructor(private http: HttpClient) {} + + changePassword(passwordWrapper: PasswordWrapper): Observable { + return this.http.put('/api/account/changePassword', passwordWrapper); + } +} diff --git a/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.html b/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.html new file mode 100644 index 0000000..0113bcb --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.html @@ -0,0 +1,60 @@ + +
+
+ Card image cap +
+
+
+ + +
+
+ + +
+
+
+

{{modelError}}

+
+
+
+
+

{{result}}

+
+
+
+ + Annuler + + +
+
+
+
+
diff --git a/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.ts b/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.ts new file mode 100644 index 0000000..31811c2 --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.component.ts @@ -0,0 +1,99 @@ +import { Component, OnInit } from '@angular/core'; +import { ProfilEditionService } from './profil-edition.service'; +import { HttpEventType, HttpResponse } from '@angular/common/http'; +import { User } from '../../core/entities'; +import { AuthService } from '../../core/services/auth.service'; + +@Component({ + selector: 'app-profil-edition', + templateUrl: './profil-edition.component.html', + styles: [` + #form { + padding-bottom: 10px; + } + + .submitFormArea { + line-height: 50px; + } + + #profil-image { + cursor: pointer; + } + + #resultMsg, #errorMsg { + max-height: 0; + overflow: hidden; + transition: max-height 0.5s ease-out; + margin: 0; + } + `] +}) +export class ProfilEditionComponent implements OnInit { + model: User; + selectedFiles: FileList; + currentFileUpload: File; + progress: { percentage: number } = { percentage: 0 }; + modelError: string; + result: string; + + constructor( + private profilEditionService: ProfilEditionService, + private authService: AuthService + ) {} + + ngOnInit(): void { + this.model = this.authService.getUser(); + } + + getAvatarUrl(): string { + return this.model.image + ? `/api/images/loadAvatar/${this.model.image}` + : './assets/images/default_user.png'; + } + + triggerProfilImageChange(): void { + document.getElementById('profilImageInput').click(); + } + + uploadImage(event) { + this.selectedFiles = event.target.files; + this.progress.percentage = 0; + + this.currentFileUpload = this.selectedFiles.item(0); + // This prevents error 400 if user doesn't select any file to upload and close the input file. + if (this.currentFileUpload) { + this.profilEditionService.uploadAvatarPicture(this.currentFileUpload).subscribe(result => { + if (result.type === HttpEventType.UploadProgress) { + this.progress.percentage = Math.round(100 * result.loaded / result.total); + } else if (result instanceof HttpResponse) { + console.log('File ' + result.body + ' completely uploaded!'); + this.model.image = result.body as string; + this.authService.setAuthenticated(this.model); + } + this.selectedFiles = undefined; + }); + } + } + + onSubmit(): void { + this.profilEditionService.updateUser(this.model).subscribe(() => { + this.setMessage('Modification enregistrée.', false); + }, error => { + this.setMessage('L\'adresse mail saisie n\'est pas disponible.', true); + }); + } + + setMessage(message: string, error: boolean): void { + this[error ? 'modelError' : 'result'] = message; + + const resultMsgDiv = document.getElementById(error ? 'errorMsg' : 'resultMsg'); + resultMsgDiv.style.maxHeight = '64px'; + + setTimeout(() => { + resultMsgDiv.style.maxHeight = '0px'; + setTimeout(() => { + this[error ? 'modelError' : 'result'] = undefined; + }, 550); + }, 3000); + } +} diff --git a/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.service.ts b/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.service.ts new file mode 100644 index 0000000..964275c --- /dev/null +++ b/src/main/ts-v7/src/app/account-settings/profil-edition/profil-edition.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import {HttpClient, HttpRequest, HttpEvent} from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { User } from '../../core/entities'; + +@Injectable() +export class ProfilEditionService { + constructor(private http: HttpClient) {} + + uploadAvatarPicture(file: File): Observable> { + const formData: FormData = new FormData(); + + formData.append('file', file); + + return this.http.request(new HttpRequest( + 'POST', '/api/images/uploadAvatar', formData, { + reportProgress: true, + responseType: 'text' + } + )); + } + + updateUser(user: User): Observable { + return this.http.put(`/api/account/`, user); + } +} diff --git a/src/main/ts-v7/src/app/app.module.ts b/src/main/ts-v7/src/app/app.module.ts index a738915..c8ec347 100755 --- a/src/main/ts-v7/src/app/app.module.ts +++ b/src/main/ts-v7/src/app/app.module.ts @@ -2,7 +2,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { RouterModule } from '@angular/router'; // Dependencies @@ -11,6 +11,9 @@ import { MDBBootstrapModule } from 'angular-bootstrap-md'; // Router import { appRoutes } from './app.routing'; +// Guard +import { AuthGuard } from './core/guards/auth.guard'; + // Components import { AppComponent } from './app.component'; import { HeaderComponent } from './header/header.component'; @@ -18,6 +21,9 @@ import { LoginComponent } from './login/login.component'; import { DisconnectionComponent } from './disconnection/disconnection.component'; import { HomeComponent } from './home/home.component'; import { MyPostsComponent } from './posts/myPosts/my-posts.component'; +import { AccountSettingsComponent } from './account-settings/account-settings.component'; +import { ChangePasswordComponent } from './account-settings/change-password/change-password.component'; +import { ProfilEditionComponent } from './account-settings/profil-edition/profil-edition.component'; // Reusable components import { PostCardComponent } from './core/post-card/post-card.component'; @@ -29,6 +35,9 @@ import { AuthService } from './core/services/auth.service'; import { HomeService } from './home/home.service'; import { LoginService } from './login/login.service'; import { MyPostsService } from './posts/myPosts/my-posts.service'; +import { ChangePasswordService } from './account-settings/change-password/change-password.service'; +import { ProfilEditionService } from './account-settings/profil-edition/profil-edition.service'; +import { UnauthorizedInterceptor } from './core/interceptors/unauthorized.interceptor'; @NgModule({ declarations: [ @@ -39,7 +48,10 @@ import { MyPostsService } from './posts/myPosts/my-posts.service'; HomeComponent, PostCardComponent, SpinnerComponent, - MyPostsComponent + MyPostsComponent, + AccountSettingsComponent, + ChangePasswordComponent, + ProfilEditionComponent ], imports: [ BrowserModule, @@ -54,11 +66,15 @@ import { MyPostsService } from './posts/myPosts/my-posts.service'; ) ], providers: [ + AuthGuard, HeaderService, AuthService, HomeService, LoginService, - MyPostsService + MyPostsService, + ChangePasswordService, + ProfilEditionService, + { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true } ], bootstrap: [AppComponent] }) diff --git a/src/main/ts-v7/src/app/app.routing.ts b/src/main/ts-v7/src/app/app.routing.ts index 5e1627c..b3416af 100755 --- a/src/main/ts-v7/src/app/app.routing.ts +++ b/src/main/ts-v7/src/app/app.routing.ts @@ -1,14 +1,20 @@ import { Routes } from '@angular/router'; + +import { AuthGuard } from './core/guards/auth.guard'; + import { LoginComponent } from './login/login.component'; import { HomeComponent } from './home/home.component'; import { DisconnectionComponent } from './disconnection/disconnection.component'; import { MyPostsComponent } from './posts/myPosts/my-posts.component'; +import { AccountSettingsComponent } from './account-settings/account-settings.component'; + export const appRoutes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'login', component: LoginComponent }, { path: 'disconnection', component: DisconnectionComponent }, - { path: 'myPosts', component: MyPostsComponent}, + { path: 'myPosts', component: MyPostsComponent, canActivate: [AuthGuard] }, + { path: 'accountSettings', component: AccountSettingsComponent, canActivate: [AuthGuard] }, { path: '**', redirectTo: '/home' } ]; diff --git a/src/main/ts-v7/src/app/core/entities.ts b/src/main/ts-v7/src/app/core/entities.ts index 926c94b..6c7a445 100755 --- a/src/main/ts-v7/src/app/core/entities.ts +++ b/src/main/ts-v7/src/app/core/entities.ts @@ -72,4 +72,4 @@ export class VersionRevision { public version: Version, public bugfix: boolean ) { } -} \ No newline at end of file +} diff --git a/src/main/ts-v7/src/app/core/guards/auth.guard.ts b/src/main/ts-v7/src/app/core/guards/auth.guard.ts new file mode 100644 index 0000000..ba0ec48 --- /dev/null +++ b/src/main/ts-v7/src/app/core/guards/auth.guard.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; + +import { AuthService } from '../services/auth.service'; + +@Injectable() +export class AuthGuard implements CanActivate { + + constructor( + private router: Router, + private authService: AuthService + ) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + let result: boolean; + + if (this.authService.isAuthenticated()) { + result = true; + } else { + result = false; + this.router.navigate(['/login']); + } + + return result; + } +} diff --git a/src/main/ts-v7/src/app/core/interceptors/unauthorized.interceptor.ts b/src/main/ts-v7/src/app/core/interceptors/unauthorized.interceptor.ts new file mode 100644 index 0000000..f795113 --- /dev/null +++ b/src/main/ts-v7/src/app/core/interceptors/unauthorized.interceptor.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { AuthService } from '../services/auth.service'; + +@Injectable() +export class UnauthorizedInterceptor implements HttpInterceptor { + constructor( + private router: Router, + private authService: AuthService + ) {} + + intercept(pRequest: HttpRequest, pNext: HttpHandler): Observable> { + return pNext.handle(pRequest).pipe(catchError(err => { + if (err.status === 401) { + this.authService.setAnonymous(); + this.router.navigate(['/login']); + } + + const error = err.error.message || err.statusText; + return throwError(error); + })); + } +}