Skip to content

Commit

Permalink
Merge pull request #7 from renantee/course-project
Browse files Browse the repository at this point in the history
Blog Application with Angular and Node JS (Updated)
  • Loading branch information
renantee authored Oct 31, 2020
2 parents 0c056e7 + fcbc7cb commit 1e34d8a
Show file tree
Hide file tree
Showing 32 changed files with 436 additions and 312 deletions.
11 changes: 9 additions & 2 deletions blog-app/client/src/app/_helpers/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@ export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private accountService: AccountService
) {}
) { }

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const user = this.accountService.userValue;
if (user) {
// check if route is restricted by role
if (route.data.roles && route.data.roles.indexOf(user.role) === -1) {
// role not authorised so redirect to home page
this.router.navigate(['/']);
return false;
}

// authorised so return true
return true;
}

// not logged in so redirect to login page with the return url
this.router.navigate(['/account/login'], { queryParams: { returnUrl: state.url }});
this.router.navigate(['/account/login'], { queryParams: { returnUrl: state.url } });
return false;
}
}
9 changes: 4 additions & 5 deletions blog-app/client/src/app/_helpers/error.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ import { AccountService } from '@app/_services';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) {}
constructor(private accountService: AccountService) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(catchError(err => {
if ([401, 403].includes(err.status) && this.accountService.userValue) {
// auto logout if 401 or 403 response returned from api
if ([401, 403].indexOf(err.status) !== -1) {
// auto logout if 401 Unauthorized or 403 Forbidden response returned from api
this.accountService.logout();
}

const error = err.error?.message || err.statusText;
console.error(err);
const error = err.error.message || err.statusText;
return throwError(error);
}))
}
Expand Down
4 changes: 2 additions & 2 deletions blog-app/client/src/app/_helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './auth.guard';
export * from './error.interceptor';
export * from './jwt.interceptor';
export * from './fake-backend';
export * from './fake-backend';
export * from './jwt.interceptor';
2 changes: 1 addition & 1 deletion blog-app/client/src/app/_helpers/jwt.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class JwtInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add auth header with jwt if user is logged in and request is to the api url
// add auth header with jwt if user is logged in and request is to api url
const user = this.accountService.userValue;
const isLoggedIn = user && user.token;
const isApiUrl = request.url.startsWith(environment.apiUrl);
Expand Down
1 change: 1 addition & 0 deletions blog-app/client/src/app/_models/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './alert';
export * from './post';
export * from './role';
export * from './user';
1 change: 1 addition & 0 deletions blog-app/client/src/app/_models/post.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export class Post {
id: string;
author: string;
title: string;
content: string;
date: Date;
Expand Down
4 changes: 4 additions & 0 deletions blog-app/client/src/app/_models/role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum Role {
User = 'User',
Admin = 'Admin'
}
8 changes: 6 additions & 2 deletions blog-app/client/src/app/_models/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
export class User {
import { Role } from "./role";

export class User {
_id: string;
id: string;
username: string;
password: string;
firstName: string;
lastName: string;
token: string;
role: Role;
token?: string;
}
4 changes: 4 additions & 0 deletions blog-app/client/src/app/_services/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export class AccountService {
return this.http.get<User>(`${environment.apiUrl}/users/${id}`);
}

getCurrentUser() {
return this.http.get<User[]>(`${environment.apiUrl}/users/current`);
}

update(id, params) {
return this.http.put(`${environment.apiUrl}/users/${id}`, params)
.pipe(map(x => {
Expand Down
2 changes: 1 addition & 1 deletion blog-app/client/src/app/_services/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './account.service';
export * from './alert.service';
export * from './post.service';
export * from './post.service';
7 changes: 5 additions & 2 deletions blog-app/client/src/app/_services/post.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { environment } from '@environments/environment';
import { Post } from '@app/_models';
import { Post, User } from '@app/_models';

@Injectable({ providedIn: 'root' })
export class PostService {
Expand All @@ -22,6 +21,10 @@ export class PostService {
return this.http.get<Post>(`${environment.apiUrl}/posts/${id}`);
}

getMyPosts() {
return this.http.get<User>(`${environment.apiUrl}/users/my-posts`);
}

update(id, params) {
return this.http.put(`${environment.apiUrl}/posts/${id}`, params)
.pipe(map(x => {
Expand Down
7 changes: 6 additions & 1 deletion blog-app/client/src/app/account/login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ export class LoginComponent implements OnInit {
private router: Router,
private accountService: AccountService,
private alertService: AlertService
) { }
) {
// redirect to home if already logged in
if (this.accountService.userValue) {
this.router.navigate(['/']);
}
}

ngOnInit() {
this.form = this.formBuilder.group({
Expand Down
11 changes: 11 additions & 0 deletions blog-app/client/src/app/admin/admin.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="card mt-4">
<h4 class="card-header">Admin</h4>
<div class="card-body">
<p>This page can be accessed <u>only by administrators</u>.</p>
<p class="mb-1">All users from secure (admin only) api end point:</p>
<div *ngIf="loading" class="spinner-border spinner-border-sm"></div>
<ul *ngIf="users">
<li *ngFor="let user of users">{{user.firstName}} {{user.lastName}}</li>
</ul>
</div>
</div>
21 changes: 21 additions & 0 deletions blog-app/client/src/app/admin/admin.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { first } from 'rxjs/operators';

import { User } from '@app/_models';
import { AccountService } from '@app/_services';

@Component({ templateUrl: 'admin.component.html' })
export class AdminComponent implements OnInit {
loading = false;
users: User[] = [];

constructor(private accountService: AccountService) { }

ngOnInit() {
this.loading = true;
this.accountService.getAll().pipe(first()).subscribe(users => {
this.loading = false;
this.users = users;
});
}
}
1 change: 1 addition & 0 deletions blog-app/client/src/app/admin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './admin.component';
38 changes: 34 additions & 4 deletions blog-app/client/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,47 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home';
import { AdminComponent } from './admin';
import { AuthGuard } from './_helpers';
import { Role } from './_models';

const accountModule = () => import('./account/account.module').then(x => x.AccountModule);
const postsModule = () => import('./posts/posts.module').then(x => x.PostsModule);
const usersModule = () => import('./users/users.module').then(x => x.UsersModule);

const routes: Routes = [
{ path: '', component: HomeComponent, canActivate: [AuthGuard] },
{ path: 'posts', loadChildren: postsModule, canActivate: [AuthGuard] },
{ path: 'users', loadChildren: usersModule, canActivate: [AuthGuard] },
{ path: 'account', loadChildren: accountModule },
{
path: '',
component: HomeComponent,
canActivate: [AuthGuard]
},
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
data: { roles: [Role.Admin] }
},
{
path: 'my-posts',
loadChildren: postsModule,
canActivate: [AuthGuard]
},
{
path: 'posts',
loadChildren: postsModule,
canActivate: [AuthGuard],
data: { roles: [Role.Admin] }
},
{
path: 'users',
loadChildren: usersModule,
canActivate: [AuthGuard],
data: { roles: [Role.Admin] }
},
{
path: 'account',
loadChildren: accountModule
},

// otherwise redirect to home
{ path: '**', redirectTo: '' }
Expand Down
5 changes: 3 additions & 2 deletions blog-app/client/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
<nav class="navbar navbar-expand navbar-dark bg-dark" *ngIf="user">
<div class="navbar-nav">
<a class="nav-item nav-link" routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a>
<a class="nav-item nav-link" routerLink="/users" routerLinkActive="active">Users</a>
<a class="nav-item nav-link" routerLink="/posts" routerLinkActive="active">Posts</a>
<a class="nav-item nav-link" routerLink="/my-posts" routerLinkActive="active">My Posts</a>
<a class="nav-item nav-link" routerLink="/posts" routerLinkActive="active" *ngIf="isAdmin">All Posts</a>
<a class="nav-item nav-link" routerLink="/users" routerLinkActive="active" *ngIf="isAdmin">Users</a>
<a class="nav-item nav-link" (click)="logout()">Logout</a>
</div>
</nav>
Expand Down
10 changes: 6 additions & 4 deletions blog-app/client/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Component } from '@angular/core';

import { AccountService } from './_services';
import { User } from './_models';
import { User, Role } from './_models';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent {
user: User;

constructor(
private accountService: AccountService
) {
constructor(private accountService: AccountService) {
this.accountService.user.subscribe(x => this.user = x);
}

get isAdmin() {
return this.user && this.user.role === Role.Admin;
}

logout() {
this.accountService.logout();
}
Expand Down
7 changes: 5 additions & 2 deletions blog-app/client/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { JwtInterceptor, ErrorInterceptor } from './_helpers';
import { AppComponent } from './app.component';
import { AlertComponent } from './_components';
import { HomeComponent } from './home';
import { AdminComponent } from './admin';

@NgModule({
imports: [
Expand All @@ -19,7 +21,8 @@ import { HomeComponent } from './home';
declarations: [
AppComponent,
AlertComponent,
HomeComponent
HomeComponent,
AdminComponent
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
Expand Down
16 changes: 11 additions & 5 deletions blog-app/client/src/app/home/home.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<div class="p-4">
<div class="container">
<h1>Hi {{user.firstName}}!</h1>
<p>You're logged in with Angular 10!!</p>
<p><a routerLink="/users">Manage Users</a></p>
<div class="card mt-4">
<h4 class="card-header">Home</h4>
<div class="card-body">
<p>You're logged in with Angular 10 & JWT!!</p>
<p>Your role is: <strong>{{user.role}}</strong>.</p>
<p>This page can be accessed by <u>all authenticated users</u>.</p>
<p class="mb-1">Current user from secure api end point:</p>
<div *ngIf="loading" class="spinner-border spinner-border-sm"></div>
<ul *ngIf="userFromApi">
<li>{{userFromApi.firstName}} {{userFromApi.lastName}}</li>
</ul>
</div>
</div>
15 changes: 14 additions & 1 deletion blog-app/client/src/app/home/home.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { Component } from '@angular/core';
import { first } from 'rxjs/operators';

import { User } from '@app/_models';
import { AccountService } from '@app/_services';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent {
loading = false;
user: User;
userFromApi: User;

constructor(private accountService: AccountService) {
constructor(
private accountService: AccountService
) {
this.user = this.accountService.userValue;
}

ngOnInit() {
this.loading = true;
this.accountService.getById(this.user.id).pipe(first()).subscribe(user => {
this.loading = false;
this.userFromApi = user;
});
}
}
Loading

0 comments on commit 1e34d8a

Please sign in to comment.