mirror of
https://github.com/linnovate/mean.git
synced 2026-02-01 16:46:12 +00:00
Merge pull request #1978 from arturovt/ng-9-strict
feat: enable `strict` mode and cleanup the code
This commit is contained in:
commit
5a97be51ae
@ -1,10 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { AuthService } from '@app/shared/services';
|
||||
|
||||
@Injectable()
|
||||
export class OnlyAdminUsersGuard implements CanActivate {
|
||||
canActivate() {
|
||||
const user = (<any>window).user;
|
||||
return user && user.isAdmin;
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
canActivate(): Observable<boolean> {
|
||||
return this.authService.getUser().pipe(map(user => !!user?.isAdmin));
|
||||
}
|
||||
}
|
||||
|
||||
27
src/app/app-routing.module.ts
Normal file
27
src/app/app-routing.module.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { AuthGuard } from './shared/guards';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomeComponent,
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'auth',
|
||||
loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule),
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
@ -1,26 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from '../auth/auth-guard.service';
|
||||
import { HomeComponent } from '../home/home.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomeComponent
|
||||
},
|
||||
{
|
||||
path: 'auth',
|
||||
loadChildren: () => import('../auth/auth.module').then(m => m.AuthModule)
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('../admin/admin.module').then(m => m.AdminModule)
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule],
|
||||
providers: [AuthGuard]
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
@ -1,6 +1,5 @@
|
||||
<app-header [user]="user"></app-header>
|
||||
<app-header [user]="user$ | async"></app-header>
|
||||
<div class="wrapper-app">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<footer>
|
||||
</footer>
|
||||
<footer></footer>
|
||||
|
||||
@ -1,56 +1,33 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
import { AuthService } from './auth/auth.service';
|
||||
import { merge, Observable } from 'rxjs';
|
||||
|
||||
import { User } from './shared/interfaces';
|
||||
import { AuthService } from './shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
private userSubscription: Subscription;
|
||||
public user: any;
|
||||
export class AppComponent {
|
||||
user$: Observable<User | null> = merge(
|
||||
// Init on startup
|
||||
this.authService.me(),
|
||||
// Update after login/register/logout
|
||||
this.authService.getUser()
|
||||
);
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
private domSanitizer: DomSanitizer,
|
||||
private matIconRegistry: MatIconRegistry
|
||||
private matIconRegistry: MatIconRegistry,
|
||||
private authService: AuthService
|
||||
) {
|
||||
this.registerSvgIcons();
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
// init this.user on startup
|
||||
this.authService.me().subscribe(data => {
|
||||
this.user = data.user;
|
||||
});
|
||||
|
||||
// update this.user after login/register/logout
|
||||
this.userSubscription = this.authService.$userSource.subscribe(user => {
|
||||
this.user = user;
|
||||
});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.signOut();
|
||||
this.navigate('');
|
||||
}
|
||||
|
||||
navigate(link): void {
|
||||
this.router.navigate([link]);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.userSubscription) {
|
||||
this.userSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
registerSvgIcons() {
|
||||
[
|
||||
'close',
|
||||
@ -84,7 +61,7 @@ export class AppComponent implements OnInit {
|
||||
'tow-truck',
|
||||
'transportation',
|
||||
'trolleybus',
|
||||
'water-transportation'
|
||||
'water-transportation',
|
||||
].forEach(icon => {
|
||||
this.matIconRegistry.addSvgIcon(
|
||||
icon,
|
||||
|
||||
@ -1,47 +1,43 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, PreloadAllModules } from '@angular/router';
|
||||
import { NgModule, APP_INITIALIZER } from '@angular/core';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AdminModule } from './admin/admin.module';
|
||||
import { AuthHeaderInterceptor } from './interceptors/header.interceptor';
|
||||
import { CatchErrorInterceptor } from './interceptors/http-error.interceptor';
|
||||
|
||||
import { AppRoutingModule } from './app-routing/app-routing.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { AuthService } from './shared/services';
|
||||
|
||||
export function appInitializerFactory(authService: AuthService) {
|
||||
return () => authService.checkTheUserOnTheFirstLoad();
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeaderComponent,
|
||||
HomeComponent,
|
||||
imports: [BrowserAnimationsModule, HttpClientModule, SharedModule, AppRoutingModule],
|
||||
declarations: [AppComponent, HeaderComponent, HomeComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthHeaderInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: CatchErrorInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: appInitializerFactory,
|
||||
multi: true,
|
||||
deps: [AuthService],
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
RouterModule,
|
||||
SharedModule,
|
||||
AuthModule,
|
||||
AdminModule,
|
||||
AppRoutingModule,
|
||||
],
|
||||
providers: [{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthHeaderInterceptor,
|
||||
multi: true,
|
||||
}, {
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: CatchErrorInterceptor,
|
||||
multi: true,
|
||||
}],
|
||||
entryComponents: [],
|
||||
bootstrap: [AppComponent]
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule { }
|
||||
export class AppModule {}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(public router: Router) {}
|
||||
|
||||
canActivate() {
|
||||
const user = (<any>window).user;
|
||||
if (user) return true;
|
||||
|
||||
// not logged in so redirect to login page with the return url
|
||||
this.router.navigate(['/auth/login']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,27 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { AuthService } from './auth.service';
|
||||
import { TokenStorage } from './token.storage';
|
||||
import { AuthRoutingModule } from './auth-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
AuthRoutingModule,
|
||||
],
|
||||
declarations: [
|
||||
LoginComponent,
|
||||
RegisterComponent
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
TokenStorage
|
||||
]
|
||||
imports: [SharedModule, AuthRoutingModule],
|
||||
declarations: [LoginComponent, RegisterComponent],
|
||||
})
|
||||
export class AuthModule { }
|
||||
export class AuthModule {}
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import { TokenStorage } from './token.storage';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
||||
constructor(private http : HttpClient, private token: TokenStorage) {}
|
||||
|
||||
public $userSource = new Subject<any>();
|
||||
|
||||
login(email : string, password : string) : Observable <any> {
|
||||
return Observable.create(observer => {
|
||||
this.http.post('/api/auth/login', {
|
||||
email,
|
||||
password
|
||||
}).subscribe((data : any) => {
|
||||
observer.next({user: data.user});
|
||||
this.setUser(data.user);
|
||||
this.token.saveToken(data.token);
|
||||
observer.complete();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
register(fullname : string, email : string, password : string, repeatPassword : string) : Observable <any> {
|
||||
return Observable.create(observer => {
|
||||
this.http.post('/api/auth/register', {
|
||||
fullname,
|
||||
email,
|
||||
password,
|
||||
repeatPassword
|
||||
}).subscribe((data : any) => {
|
||||
observer.next({user: data.user});
|
||||
this.setUser(data.user);
|
||||
this.token.saveToken(data.token);
|
||||
observer.complete();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setUser(user): void {
|
||||
if (user) user.isAdmin = (user.roles.indexOf('admin') > -1);
|
||||
this.$userSource.next(user);
|
||||
(<any>window).user = user;
|
||||
}
|
||||
|
||||
getUser(): Observable<any> {
|
||||
return this.$userSource.asObservable();
|
||||
}
|
||||
|
||||
me(): Observable<any> {
|
||||
return Observable.create(observer => {
|
||||
const tokenVal = this.token.getToken();
|
||||
if (!tokenVal) return observer.complete();
|
||||
this.http.get('/api/auth/me').subscribe((data : any) => {
|
||||
observer.next({user: data.user});
|
||||
this.setUser(data.user);
|
||||
observer.complete();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
signOut(): void {
|
||||
this.token.signOut();
|
||||
this.setUser(null);
|
||||
delete (<any>window).user;
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
// export * from './auth.module';
|
||||
|
||||
@ -1,28 +1,22 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import {AuthService} from '../auth.service';
|
||||
import { AuthService } from '@app/shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['../auth.component.scss']
|
||||
styleUrls: ['../auth.component.scss'],
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
export class LoginComponent {
|
||||
email: string | null = null;
|
||||
password: string | null = null;
|
||||
|
||||
constructor(private authService: AuthService, private router: Router) { }
|
||||
|
||||
email: string;
|
||||
password: string;
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
constructor(private router: Router, private authService: AuthService) {}
|
||||
|
||||
login(): void {
|
||||
this.authService.login(this.email, this.password)
|
||||
.subscribe(data => {
|
||||
this.router.navigate(['']);
|
||||
})
|
||||
this.authService.login(this.email!, this.password!).subscribe(() => {
|
||||
this.router.navigateByUrl('/');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<td>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Email" formControlName="email" name="email" required>
|
||||
<mat-error *ngIf="email.invalid && email.errors.email">Invalid email address</mat-error>
|
||||
<mat-error *ngIf="email.invalid && email.hasError('email')">Invalid email address</mat-error>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</tr>
|
||||
@ -31,7 +31,7 @@
|
||||
<td>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Repeat Password" formControlName="repeatPassword" type="password" name="repeatPassword" required>
|
||||
<mat-error *ngIf="repeatPassword.invalid && repeatPassword.errors.passwordMatch">Password mismatch</mat-error>
|
||||
<mat-error *ngIf="repeatPassword.invalid && repeatPassword.hasError('passwordMatch')">Password mismatch</mat-error>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -1,56 +1,64 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { FormGroup, FormControl, Validators, ValidationErrors } from '@angular/forms';
|
||||
import {
|
||||
FormGroup,
|
||||
FormControl,
|
||||
Validators,
|
||||
ValidationErrors,
|
||||
AbstractControl,
|
||||
} from '@angular/forms';
|
||||
|
||||
|
||||
import {AuthService} from '../auth.service';
|
||||
import { AuthService } from '@app/shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: './register.component.html',
|
||||
styleUrls: ['../auth.component.scss']
|
||||
styleUrls: ['../auth.component.scss'],
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
export class RegisterComponent {
|
||||
constructor(private router: Router, private authService: AuthService) {}
|
||||
|
||||
constructor(private authService: AuthService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
passwordsMatchValidator(control: FormControl): ValidationErrors {
|
||||
let password = control.root.get('password');
|
||||
return password && control.value !== password.value ? {
|
||||
passwordMatch: true
|
||||
}: null;
|
||||
passwordsMatchValidator(control: FormControl): ValidationErrors | null {
|
||||
const password = control.root.get('password');
|
||||
return password && control.value !== password.value
|
||||
? {
|
||||
passwordMatch: true,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
userForm = new FormGroup({
|
||||
fullname: new FormControl('', [Validators.required]),
|
||||
email: new FormControl('', [Validators.required, Validators.email]),
|
||||
password: new FormControl('', [Validators.required]),
|
||||
repeatPassword: new FormControl('', [Validators.required, this.passwordsMatchValidator])
|
||||
})
|
||||
repeatPassword: new FormControl('', [Validators.required, this.passwordsMatchValidator]),
|
||||
});
|
||||
|
||||
get fullname(): any { return this.userForm.get('fullname'); }
|
||||
get email(): any { return this.userForm.get('email'); }
|
||||
get password(): any { return this.userForm.get('password'); }
|
||||
get repeatPassword(): any { return this.userForm.get('repeatPassword'); }
|
||||
|
||||
register() {
|
||||
|
||||
if(!this.userForm.valid) return;
|
||||
|
||||
let {
|
||||
fullname,
|
||||
email,
|
||||
password,
|
||||
repeatPassword
|
||||
} = this.userForm.getRawValue();
|
||||
|
||||
this.authService.register(fullname, email, password, repeatPassword)
|
||||
.subscribe(data => {
|
||||
this.router.navigate(['']);
|
||||
})
|
||||
get fullname(): AbstractControl {
|
||||
return this.userForm.get('fullname')!;
|
||||
}
|
||||
|
||||
get email(): AbstractControl {
|
||||
return this.userForm.get('email')!;
|
||||
}
|
||||
|
||||
get password(): AbstractControl {
|
||||
return this.userForm.get('password')!;
|
||||
}
|
||||
|
||||
get repeatPassword(): AbstractControl {
|
||||
return this.userForm.get('repeatPassword')!;
|
||||
}
|
||||
|
||||
register(): void {
|
||||
if (this.userForm.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { fullname, email, password, repeatPassword } = this.userForm.getRawValue();
|
||||
|
||||
this.authService.register(fullname, email, password, repeatPassword).subscribe(data => {
|
||||
this.router.navigate(['']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
|
||||
const TOKEN_KEY = 'AuthToken';
|
||||
|
||||
@Injectable()
|
||||
export class TokenStorage {
|
||||
|
||||
constructor() { }
|
||||
|
||||
signOut() {
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
||||
public saveToken(token: string) {
|
||||
if (!token) return;
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
window.localStorage.setItem(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
public getToken(): string {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,14 @@
|
||||
<header>
|
||||
<mat-toolbar color="primary">
|
||||
<a [routerLink]="['/']" class="logo"></a>
|
||||
<a routerLink="/" class="logo"></a>
|
||||
<span class="example-spacer"></span>
|
||||
<a class="links side" [routerLink]="['/auth/login']" *ngIf="!user">Login</a>
|
||||
<a class="links side" routerLink="/auth/login" *ngIf="!user">Login</a>
|
||||
<div>
|
||||
<a class="links side" *ngIf="user" [matMenuTriggerFor]="menu">
|
||||
<mat-icon>account_circle</mat-icon>{{user.fullname}}
|
||||
<mat-icon>account_circle</mat-icon>{{ user.fullname }}
|
||||
</a>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item *ngIf="user && user.isAdmin" [routerLink]="['/admin']">admin</button>
|
||||
<button mat-menu-item *ngIf="user?.isAdmin" routerLink="/admin">admin</button>
|
||||
<button mat-menu-item (click)="logout()">logout</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
@ -1,32 +1,22 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { User } from '@app/shared/interfaces';
|
||||
|
||||
import { AuthService } from '@app/shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss']
|
||||
styleUrls: ['./header.component.scss'],
|
||||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
export class HeaderComponent {
|
||||
@Input() user: User | null = null;
|
||||
|
||||
@Input() user: any = {};
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
constructor(private router: Router, private authService: AuthService) {}
|
||||
|
||||
logout(): void {
|
||||
this.authService.signOut();
|
||||
this.navigate('/auth/login');
|
||||
this.router.navigateByUrl('/auth/login');
|
||||
}
|
||||
|
||||
navigate(link): void {
|
||||
this.router.navigate([link]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { TokenStorage } from '../auth/token.storage';
|
||||
import { AuthService } from '@app/shared/services';
|
||||
|
||||
@Injectable()
|
||||
export class AuthHeaderInterceptor implements HttpInterceptor {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// Clone the request to add the new header
|
||||
const token = new TokenStorage();
|
||||
const tokenVal = token.getToken();
|
||||
const clonedRequest = req.clone({
|
||||
headers: req.headers.set('Authorization', tokenVal ? `Bearer ${tokenVal}` : '')
|
||||
req = req.clone({
|
||||
setHeaders: this.authService.getAuthorizationHeaders(),
|
||||
});
|
||||
|
||||
// Pass the cloned request instead of the original request to the next handle
|
||||
return next.handle(clonedRequest);
|
||||
return next.handle(req);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,24 +4,30 @@ import {
|
||||
HttpInterceptor,
|
||||
HttpHandler,
|
||||
HttpRequest,
|
||||
HttpErrorResponse
|
||||
HttpErrorResponse,
|
||||
} from '@angular/common/http';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class CatchErrorInterceptor implements HttpInterceptor {
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(request).pipe(
|
||||
catchError(error => {
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
const text =
|
||||
error.error && error.error.message ? error.error.message : error.statusText;
|
||||
(<any>window).globalEvents.emit('open error dialog', text);
|
||||
}
|
||||
constructor(private snackBar: MatSnackBar) {}
|
||||
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(request).pipe(catchError(this.showSnackBar));
|
||||
}
|
||||
|
||||
private showSnackBar = (response: HttpErrorResponse): Observable<never> => {
|
||||
const text: string | undefined = response.error?.message ?? response.error.statusText;
|
||||
|
||||
if (text) {
|
||||
this.snackBar.open(text, 'Close', {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
return throwError(response);
|
||||
};
|
||||
}
|
||||
|
||||
25
src/app/shared/guards/auth.guard.ts
Normal file
25
src/app/shared/guards/auth.guard.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { AuthService } from '../services';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private router: Router, private authService: AuthService) {}
|
||||
|
||||
canActivate(): Observable<boolean> {
|
||||
return this.authService.getUser().pipe(
|
||||
map(user => {
|
||||
if (user !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.router.navigateByUrl('/auth/login');
|
||||
return false;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
1
src/app/shared/guards/index.ts
Normal file
1
src/app/shared/guards/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './auth.guard';
|
||||
1
src/app/shared/interfaces/index.ts
Normal file
1
src/app/shared/interfaces/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './user.interface';
|
||||
7
src/app/shared/interfaces/user.interface.ts
Normal file
7
src/app/shared/interfaces/user.interface.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface User {
|
||||
_id: string;
|
||||
fullname: string;
|
||||
createdAt: string;
|
||||
roles: string[];
|
||||
isAdmin: boolean;
|
||||
}
|
||||
100
src/app/shared/services/auth/auth.service.ts
Normal file
100
src/app/shared/services/auth/auth.service.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { Observable, BehaviorSubject, EMPTY } from 'rxjs';
|
||||
import { tap, pluck } from 'rxjs/operators';
|
||||
|
||||
import { User } from '@app/shared/interfaces';
|
||||
|
||||
import { TokenStorage } from './token.storage';
|
||||
|
||||
interface AuthResponse {
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthService {
|
||||
private user$ = new BehaviorSubject<User | null>(null);
|
||||
|
||||
constructor(private http: HttpClient, private tokenStorage: TokenStorage) {}
|
||||
|
||||
login(email: string, password: string): Observable<User> {
|
||||
return this.http
|
||||
.post<AuthResponse>('/api/auth/login', { email, password })
|
||||
.pipe(
|
||||
tap(({ token, user }) => {
|
||||
this.setUser(user);
|
||||
this.tokenStorage.saveToken(token);
|
||||
}),
|
||||
pluck('user')
|
||||
);
|
||||
}
|
||||
|
||||
register(
|
||||
fullname: string,
|
||||
email: string,
|
||||
password: string,
|
||||
repeatPassword: string
|
||||
): Observable<User> {
|
||||
return this.http
|
||||
.post<AuthResponse>('/api/auth/register', {
|
||||
fullname,
|
||||
email,
|
||||
password,
|
||||
repeatPassword,
|
||||
})
|
||||
.pipe(
|
||||
tap(({ token, user }) => {
|
||||
this.setUser(user);
|
||||
this.tokenStorage.saveToken(token);
|
||||
}),
|
||||
pluck('user')
|
||||
);
|
||||
}
|
||||
|
||||
setUser(user: User | null): void {
|
||||
if (user) {
|
||||
user.isAdmin = user.roles.includes('admin');
|
||||
}
|
||||
|
||||
this.user$.next(user);
|
||||
window.user = user;
|
||||
}
|
||||
|
||||
getUser(): Observable<User | null> {
|
||||
return this.user$.asObservable();
|
||||
}
|
||||
|
||||
me(): Observable<User> {
|
||||
const token: string | null = this.tokenStorage.getToken();
|
||||
|
||||
if (token === null) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return this.http.get<AuthResponse>('/api/auth/me').pipe(
|
||||
tap(({ user }) => this.setUser(user)),
|
||||
pluck('user')
|
||||
);
|
||||
}
|
||||
|
||||
signOut(): void {
|
||||
this.tokenStorage.signOut();
|
||||
this.setUser(null);
|
||||
delete window.user;
|
||||
}
|
||||
|
||||
getAuthorizationHeaders() {
|
||||
const token: string | null = this.tokenStorage.getToken() || '';
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
/**
|
||||
* Let's try to get user's information if he was logged in previously,
|
||||
* thus we can ensure that the user is able to access the `/` (home) page.
|
||||
*/
|
||||
checkTheUserOnTheFirstLoad(): Promise<User> {
|
||||
return this.me().toPromise();
|
||||
}
|
||||
}
|
||||
20
src/app/shared/services/auth/token.storage.ts
Normal file
20
src/app/shared/services/auth/token.storage.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TokenStorage {
|
||||
private tokenKey = 'authToken';
|
||||
|
||||
signOut(): void {
|
||||
localStorage.removeItem(this.tokenKey);
|
||||
localStorage.clear();
|
||||
}
|
||||
|
||||
saveToken(token?: string): void {
|
||||
if (!token) return;
|
||||
localStorage.setItem(this.tokenKey, token);
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
return localStorage.getItem(this.tokenKey);
|
||||
}
|
||||
}
|
||||
1
src/app/shared/services/index.ts
Normal file
1
src/app/shared/services/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './auth/auth.service';
|
||||
@ -1,16 +1,33 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mean</title>
|
||||
<base href="/">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Mean</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Exo:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
|
||||
<link
|
||||
rel="prefetch"
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
as="style"
|
||||
crossorigin
|
||||
/>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://fonts.googleapis.com/css?family=Exo:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i"
|
||||
as="style"
|
||||
crossorigin
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Exo:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
(window as any).global = window;
|
||||
(window as any).globalEvents = new EventEmitter();
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
8
src/typings.d.ts
vendored
8
src/typings.d.ts
vendored
@ -1,4 +1,12 @@
|
||||
import { User } from '@app/shared/interfaces';
|
||||
|
||||
declare module '*.json' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
user: User | null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext"
|
||||
},
|
||||
"files": [
|
||||
"src/polyfills.ts",
|
||||
"src/main.ts"
|
||||
|
||||
@ -14,7 +14,9 @@
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"importHelpers": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user