From 509e09656892aa5a46972b99d9b244a60291ff32 Mon Sep 17 00:00:00 2001 From: "Renante D. Entera" Date: Sat, 24 Oct 2020 15:29:18 +0800 Subject: [PATCH 1/5] Course Project: Angular - Role Based Authorization Updates include: > Blog Application: Angular - Role Based Authorization Fixing bugs: > `bug-compile-problems-part-2.png` = 7 problems occur when compiling Angular project --- .../client/src/app/_helpers/auth.guard.ts | 17 +++++--- .../src/app/_helpers/error.interceptor.ts | 13 +++--- blog-app/client/src/app/_helpers/index.ts | 4 +- .../src/app/_helpers/jwt.interceptor.ts | 8 ++-- blog-app/client/src/app/_models/index.ts | 1 + blog-app/client/src/app/_models/role.ts | 4 ++ blog-app/client/src/app/_models/user.ts | 7 ++- .../app/_services/authentication.service.ts | 43 +++++++++++++++++++ blog-app/client/src/app/_services/index.ts | 2 + .../client/src/app/_services/user.service.ts | 18 ++++++++ .../client/src/app/account/login.component.ts | 7 ++- .../client/src/app/admin/admin.component.html | 11 +++++ .../client/src/app/admin/admin.component.ts | 21 +++++++++ blog-app/client/src/app/admin/index.ts | 1 + blog-app/client/src/app/app-routing.module.ts | 33 ++++++++++++-- blog-app/client/src/app/app.component.html | 4 +- blog-app/client/src/app/app.component.ts | 17 +++++--- blog-app/client/src/app/app.module.ts | 7 ++- .../client/src/app/home/home.component.html | 16 ++++--- .../client/src/app/home/home.component.ts | 20 +++++++-- 20 files changed, 210 insertions(+), 44 deletions(-) create mode 100644 blog-app/client/src/app/_models/role.ts create mode 100644 blog-app/client/src/app/_services/authentication.service.ts create mode 100644 blog-app/client/src/app/_services/user.service.ts create mode 100644 blog-app/client/src/app/admin/admin.component.html create mode 100644 blog-app/client/src/app/admin/admin.component.ts create mode 100644 blog-app/client/src/app/admin/index.ts diff --git a/blog-app/client/src/app/_helpers/auth.guard.ts b/blog-app/client/src/app/_helpers/auth.guard.ts index 6f26abc..bd879e8 100644 --- a/blog-app/client/src/app/_helpers/auth.guard.ts +++ b/blog-app/client/src/app/_helpers/auth.guard.ts @@ -1,24 +1,31 @@ import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { AccountService } from '@app/_services'; +import { AuthenticationService } from '@app/_services'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor( private router: Router, - private accountService: AccountService - ) {} + private authenticationService: AuthenticationService + ) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { - const user = this.accountService.userValue; + const user = this.authenticationService.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; } } \ No newline at end of file diff --git a/blog-app/client/src/app/_helpers/error.interceptor.ts b/blog-app/client/src/app/_helpers/error.interceptor.ts index 9a74879..92855b7 100644 --- a/blog-app/client/src/app/_helpers/error.interceptor.ts +++ b/blog-app/client/src/app/_helpers/error.interceptor.ts @@ -3,21 +3,20 @@ import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/c import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; -import { AccountService } from '@app/_services'; +import { AuthenticationService } from '@app/_services'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { - constructor(private accountService: AccountService) {} + constructor(private authenticationService: AuthenticationService) { } intercept(request: HttpRequest, next: HttpHandler): Observable> { 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 - this.accountService.logout(); + if ([401, 403].indexOf(err.status) !== -1) { + // auto logout if 401 Unauthorized or 403 Forbidden response returned from api + this.authenticationService.logout(); } - const error = err.error?.message || err.statusText; - console.error(err); + const error = err.error.message || err.statusText; return throwError(error); })) } diff --git a/blog-app/client/src/app/_helpers/index.ts b/blog-app/client/src/app/_helpers/index.ts index f7fb1b6..175f9e2 100644 --- a/blog-app/client/src/app/_helpers/index.ts +++ b/blog-app/client/src/app/_helpers/index.ts @@ -1,4 +1,4 @@ export * from './auth.guard'; export * from './error.interceptor'; -export * from './jwt.interceptor'; -export * from './fake-backend'; \ No newline at end of file +export * from './fake-backend'; +export * from './jwt.interceptor'; \ No newline at end of file diff --git a/blog-app/client/src/app/_helpers/jwt.interceptor.ts b/blog-app/client/src/app/_helpers/jwt.interceptor.ts index 85a2550..be1fd55 100644 --- a/blog-app/client/src/app/_helpers/jwt.interceptor.ts +++ b/blog-app/client/src/app/_helpers/jwt.interceptor.ts @@ -3,15 +3,15 @@ import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/c import { Observable } from 'rxjs'; import { environment } from '@environments/environment'; -import { AccountService } from '@app/_services'; +import { AuthenticationService } from '@app/_services'; @Injectable() export class JwtInterceptor implements HttpInterceptor { - constructor(private accountService: AccountService) { } + constructor(private authenticationService: AuthenticationService) { } intercept(request: HttpRequest, next: HttpHandler): Observable> { - // add auth header with jwt if user is logged in and request is to the api url - const user = this.accountService.userValue; + // add auth header with jwt if user is logged in and request is to api url + const user = this.authenticationService.userValue; const isLoggedIn = user && user.token; const isApiUrl = request.url.startsWith(environment.apiUrl); if (isLoggedIn && isApiUrl) { diff --git a/blog-app/client/src/app/_models/index.ts b/blog-app/client/src/app/_models/index.ts index 8622180..7732e94 100644 --- a/blog-app/client/src/app/_models/index.ts +++ b/blog-app/client/src/app/_models/index.ts @@ -1,3 +1,4 @@ export * from './alert'; export * from './post'; +export * from './role'; export * from './user'; \ No newline at end of file diff --git a/blog-app/client/src/app/_models/role.ts b/blog-app/client/src/app/_models/role.ts new file mode 100644 index 0000000..02d7863 --- /dev/null +++ b/blog-app/client/src/app/_models/role.ts @@ -0,0 +1,4 @@ +export enum Role { + User = 'User', + Admin = 'Admin' +} \ No newline at end of file diff --git a/blog-app/client/src/app/_models/user.ts b/blog-app/client/src/app/_models/user.ts index 8b0f433..d109af7 100644 --- a/blog-app/client/src/app/_models/user.ts +++ b/blog-app/client/src/app/_models/user.ts @@ -1,8 +1,11 @@ -export class User { +import { Role } from "./role"; + +export class User { id: string; username: string; password: string; firstName: string; lastName: string; - token: string; + role: Role; + token?: string; } \ No newline at end of file diff --git a/blog-app/client/src/app/_services/authentication.service.ts b/blog-app/client/src/app/_services/authentication.service.ts new file mode 100644 index 0000000..5f2f722 --- /dev/null +++ b/blog-app/client/src/app/_services/authentication.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { environment } from '@environments/environment'; +import { User } from '@app/_models'; + +@Injectable({ providedIn: 'root' }) +export class AuthenticationService { + private userSubject: BehaviorSubject; + public user: Observable; + + constructor( + private router: Router, + private http: HttpClient + ) { + this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user'))); + this.user = this.userSubject.asObservable(); + } + + public get userValue(): User { + return this.userSubject.value; + } + + login(username: string, password: string) { + return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, password }) + .pipe(map(user => { + // store user details and jwt token in local storage to keep user logged in between page refreshes + localStorage.setItem('user', JSON.stringify(user)); + this.userSubject.next(user); + return user; + })); + } + + logout() { + // remove user from local storage to log user out + localStorage.removeItem('user'); + this.userSubject.next(null); + this.router.navigate(['/account/login']); + } +} \ No newline at end of file diff --git a/blog-app/client/src/app/_services/index.ts b/blog-app/client/src/app/_services/index.ts index 1dae50e..1783b82 100644 --- a/blog-app/client/src/app/_services/index.ts +++ b/blog-app/client/src/app/_services/index.ts @@ -1,3 +1,5 @@ export * from './account.service'; export * from './alert.service'; +export * from './authentication.service'; export * from './post.service'; +export * from './user.service'; \ No newline at end of file diff --git a/blog-app/client/src/app/_services/user.service.ts b/blog-app/client/src/app/_services/user.service.ts new file mode 100644 index 0000000..aee90b9 --- /dev/null +++ b/blog-app/client/src/app/_services/user.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { environment } from '@environments/environment'; +import { User } from '@app/_models'; + +@Injectable({ providedIn: 'root' }) +export class UserService { + constructor(private http: HttpClient) { } + + getAll() { + return this.http.get(`${environment.apiUrl}/users`); + } + + getById(id: string) { + return this.http.get(`${environment.apiUrl}/users/${id}`); + } +} \ No newline at end of file diff --git a/blog-app/client/src/app/account/login.component.ts b/blog-app/client/src/app/account/login.component.ts index b90dea8..78ed464 100644 --- a/blog-app/client/src/app/account/login.component.ts +++ b/blog-app/client/src/app/account/login.component.ts @@ -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({ diff --git a/blog-app/client/src/app/admin/admin.component.html b/blog-app/client/src/app/admin/admin.component.html new file mode 100644 index 0000000..4194907 --- /dev/null +++ b/blog-app/client/src/app/admin/admin.component.html @@ -0,0 +1,11 @@ +
+

Admin

+
+

This page can be accessed only by administrators.

+

All users from secure (admin only) api end point:

+
+
    +
  • {{user.firstName}} {{user.lastName}}
  • +
+
+
\ No newline at end of file diff --git a/blog-app/client/src/app/admin/admin.component.ts b/blog-app/client/src/app/admin/admin.component.ts new file mode 100644 index 0000000..b913909 --- /dev/null +++ b/blog-app/client/src/app/admin/admin.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { first } from 'rxjs/operators'; + +import { User } from '@app/_models'; +import { UserService } from '@app/_services'; + +@Component({ templateUrl: 'admin.component.html' }) +export class AdminComponent implements OnInit { + loading = false; + users: User[] = []; + + constructor(private userService: UserService) { } + + ngOnInit() { + this.loading = true; + this.userService.getAll().pipe(first()).subscribe(users => { + this.loading = false; + this.users = users; + }); + } +} \ No newline at end of file diff --git a/blog-app/client/src/app/admin/index.ts b/blog-app/client/src/app/admin/index.ts new file mode 100644 index 0000000..c7fcd08 --- /dev/null +++ b/blog-app/client/src/app/admin/index.ts @@ -0,0 +1 @@ +export * from './admin.component'; \ No newline at end of file diff --git a/blog-app/client/src/app/app-routing.module.ts b/blog-app/client/src/app/app-routing.module.ts index 854465e..d1546c8 100644 --- a/blog-app/client/src/app/app-routing.module.ts +++ b/blog-app/client/src/app/app-routing.module.ts @@ -2,17 +2,42 @@ 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: '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: '' } diff --git a/blog-app/client/src/app/app.component.html b/blog-app/client/src/app/app.component.html index 9865dda..3e1d939 100644 --- a/blog-app/client/src/app/app.component.html +++ b/blog-app/client/src/app/app.component.html @@ -2,8 +2,8 @@ diff --git a/blog-app/client/src/app/app.component.ts b/blog-app/client/src/app/app.component.ts index b2134ae..49dbef5 100644 --- a/blog-app/client/src/app/app.component.ts +++ b/blog-app/client/src/app/app.component.ts @@ -1,19 +1,22 @@ import { Component } from '@angular/core'; +import { Router } from '@angular/router'; -import { AccountService } from './_services'; -import { User } from './_models'; +import { AuthenticationService } from './_services'; +import { User, Role } from './_models'; @Component({ selector: 'app', templateUrl: 'app.component.html' }) export class AppComponent { user: User; - constructor( - private accountService: AccountService - ) { - this.accountService.user.subscribe(x => this.user = x); + constructor(private authenticationService: AuthenticationService) { + this.authenticationService.user.subscribe(x => this.user = x); + } + + get isAdmin() { + return this.user && this.user.role === Role.Admin; } logout() { - this.accountService.logout(); + this.authenticationService.logout(); } } \ No newline at end of file diff --git a/blog-app/client/src/app/app.module.ts b/blog-app/client/src/app/app.module.ts index 83b3abe..3d49ae5 100644 --- a/blog-app/client/src/app/app.module.ts +++ b/blog-app/client/src/app/app.module.ts @@ -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: [ @@ -19,7 +21,8 @@ import { HomeComponent } from './home'; declarations: [ AppComponent, AlertComponent, - HomeComponent + HomeComponent, + AdminComponent ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, diff --git a/blog-app/client/src/app/home/home.component.html b/blog-app/client/src/app/home/home.component.html index 4a08506..391fa27 100644 --- a/blog-app/client/src/app/home/home.component.html +++ b/blog-app/client/src/app/home/home.component.html @@ -1,7 +1,13 @@ -
-
-

Hi {{user.firstName}}!

-

You're logged in with Angular 10!!

-

Manage Users

+
+

Home

+
+

You're logged in with Angular 10 & JWT!!

+

Your role is: {{user.role}}.

+

This page can be accessed by all authenticated users.

+

Current user from secure api end point:

+
+
    +
  • {{userFromApi.firstName}} {{userFromApi.lastName}}
  • +
\ No newline at end of file diff --git a/blog-app/client/src/app/home/home.component.ts b/blog-app/client/src/app/home/home.component.ts index 5b4a656..54343ed 100644 --- a/blog-app/client/src/app/home/home.component.ts +++ b/blog-app/client/src/app/home/home.component.ts @@ -1,13 +1,27 @@ import { Component } from '@angular/core'; +import { first } from 'rxjs/operators'; import { User } from '@app/_models'; -import { AccountService } from '@app/_services'; +import { UserService, AuthenticationService } from '@app/_services'; @Component({ templateUrl: 'home.component.html' }) export class HomeComponent { + loading = false; user: User; + userFromApi: User; - constructor(private accountService: AccountService) { - this.user = this.accountService.userValue; + constructor( + private userService: UserService, + private authenticationService: AuthenticationService + ) { + this.user = this.authenticationService.userValue; + } + + ngOnInit() { + this.loading = true; + this.userService.getById(this.user.id).pipe(first()).subscribe(user => { + this.loading = false; + this.userFromApi = user; + }); } } \ No newline at end of file From 15b64f0450621ab4abf8fd454fa7b7762ed33351 Mon Sep 17 00:00:00 2001 From: "Renante D. Entera" Date: Wed, 28 Oct 2020 14:55:33 +0800 Subject: [PATCH 2/5] Course Project: Node JS - Refactor API Updates include: > Update Database Structure > Refactor API Post Routes > Update API Processing --- blog-app/server/src/index.js | 197 +++------------------------ blog-app/server/src/models/Post.js | 5 + blog-app/server/src/models/User.js | 11 +- blog-app/server/src/routes/posts.js | 42 ++++++ blog-app/server/src/services/post.js | 49 +++++++ blog-app/server/src/services/user.js | 6 +- 6 files changed, 128 insertions(+), 182 deletions(-) create mode 100644 blog-app/server/src/routes/posts.js create mode 100644 blog-app/server/src/services/post.js diff --git a/blog-app/server/src/index.js b/blog-app/server/src/index.js index f6b3931..360242b 100644 --- a/blog-app/server/src/index.js +++ b/blog-app/server/src/index.js @@ -1,196 +1,43 @@ -require('dotenv').config() -require('rootpath')() +require('dotenv').config(); +require('rootpath')(); -const express = require('express') -const app = express() -const cors = require('cors') -const bodyParser = require('body-parser') -const jwt = require('src/_helpers/jwt') -const errorHandler = require('src/_helpers/error-handler') +const express = require('express'); +const app = express(); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const jwt = require('src/_helpers/jwt'); +const errorHandler = require('src/_helpers/error-handler'); -const { body, validationResult } = require('express-validator') - -app.use(bodyParser.urlencoded({ extended: false })) -app.use(bodyParser.json()) -app.use(cors()) +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); +app.use(cors()); // use JWT auth to secure the api -app.use(jwt()) - -// Post model -const Post = require('src/models/Post') - -// Error handler -function createError(message) { - return { - errors: [ - { - message - } - ] - } -} +app.use(jwt()); // Endpoint to check if API is working app.get('/', (req, res) => { res.send({ status: 'online' - }) -}) - -// Endpoint to create post -app.post( - '/api/posts/', - // Express validator middleware function to identify which - // fields to validate - [ - body('title').isString(), - body('content').isString() - ], - (req, res) => { - // Retrieve errors from function - const errors = validationResult(req) - - // If there are errors in validation, return the array of - // error messages with the status of 422 Unprocessable - // Entity - if (!errors.isEmpty()) { - return res.status(422).json({ errors: errors.array() }) - } - - // Retrieve variables from the request body - const { title, content } = req.body - - const post = new Post({ - title, - content - }) - - post.save((err, post) => { - if (err) { - return res.status(500).json({ errors: err }) - } - - return res.status(201).send(post) - }) -}) - -// Endpoint to list all the posts -app.get('/api/posts/', (req, res) => { - Post.find({}, null, { sort: { date: -1 } }, (err, posts) => { - if (err) { - return res.status(500).send( - createError(err) - ) - } - - // Return the list of posts - // status code 200 to signify successful retrieval - res.send(posts) - }) -}) - -// Endpoint to retrieve a post by its id -app.get('/api/posts/:id', (req, res) => { - // Store id in variable from the path parameter - const id = req.params.id - - Post.findOne({ id: id }, (err, post) => { - if(err) { - return res.status(400).send( - createError('Post not found') - ) - } - - // Return the post with the status code 200 - // to signify successful retrieval - res.send(post) - }) -}) - -// Endpoint update post by its id -app.put( - '/api/posts/:id', - // Express validator middleware function to identify which - // fields to validate - [ - body('title').isString(), - body('content').isString() - ], - (req, res) => { - - // Retrieve errors from function - const errors = validationResult(req) - - // If there are errors in validation, return the array of - // error messages with the status of 422 Unprocessable - // Entity - if (!errors.isEmpty()) { - return res.status(422).json({ errors: errors.array() }) - } - - // Store id in variable from the path parameter - const id = req.params.id - - // Retrieve variables from the request body - const { title, content } = req.body - - const updatedPost = { - id, - title, - content - } - - Post.updateOne({ id: id }, updatedPost, (err, post) => { - if(!post) { - return res.status(400).send( - createError('Post not found') - ) - } - - // Return the post with the status code 200 - // to signify successful retrieval - res.send(updatedPost) - }) -}) - -// Endpoint to delete post by its id -app.delete('/api/posts/:id', (req, res) => { - // Store id in variable from the path parameter - const id = req.params.id - - Post.deleteOne({ id: id }, (err) => { - if (err) { - return res.status(500).send( - createError(err) - ) - } - - // Return the post with the status code 200 - // to signify successful deletion - res.send({ - 'message': `Post with id ${id} has been successfully deleted` - }) - }) -}) + }); +}); // API routes -app.use('/api/users', require('src/routes/users')) +app.use('/api/posts', require('src/routes/posts')); +app.use('/api/users', require('src/routes/users')); // Return an error if route does not exist in our server app.all('*', (req, res) => { - return res.status(404).send( - createError('Not found') - ) -}) + return res.status(404).json({ message: 'Not Found' }); +}); // Global error handler -app.use(errorHandler) +app.use(errorHandler); // Set port -const PORT = process.env.PORT || 3000 +const PORT = process.env.PORT || 3000; // Expose endpoints to port ${PORT} app.listen(PORT, () => { - console.log(`Listening to port ${PORT}`) -}) \ No newline at end of file + console.log(`Listening to port ${PORT}`); +}); \ No newline at end of file diff --git a/blog-app/server/src/models/Post.js b/blog-app/server/src/models/Post.js index f7dff87..4e1cf82 100644 --- a/blog-app/server/src/models/Post.js +++ b/blog-app/server/src/models/Post.js @@ -13,6 +13,11 @@ const schema = new Schema({ type: Number, required: true }, + author: { + type: Schema.Types.ObjectId, + required: true, + ref: 'User' + }, title: { type: String, require: true diff --git a/blog-app/server/src/models/User.js b/blog-app/server/src/models/User.js index 8425c2d..70866f6 100644 --- a/blog-app/server/src/models/User.js +++ b/blog-app/server/src/models/User.js @@ -32,7 +32,11 @@ const schema = new Schema({ createdDate: { type: Date, default: Date.now - } + }, + posts: [{ + type: Schema.Types.ObjectId, + ref: 'Post' + }] }); schema.plugin(autoIncrement.plugin, { @@ -51,7 +55,4 @@ schema.set('toJSON', { }); const User = connection.model('User', schema); -module.exports = User; - -/*User = mongoose.model('User', schema); -module.exports = User;*/ \ No newline at end of file +module.exports = User; \ No newline at end of file diff --git a/blog-app/server/src/routes/posts.js b/blog-app/server/src/routes/posts.js new file mode 100644 index 0000000..07ace83 --- /dev/null +++ b/blog-app/server/src/routes/posts.js @@ -0,0 +1,42 @@ +const express = require('express'); +const router = express.Router(); +const postService = require('src/services/post'); + +// Routes +router.post('/', create); +router.get('/', getAll); +router.get('/:id', getById); +router.put('/:id', update); +router.delete('/:id', _delete); + +module.exports = router; + +function create(req, res, next) { + postService.create(req.body) + .then(post => res.status(201).json(post)) + .catch(err => next(err)); +} + +function getAll(req, res, next) { + postService.getAll() + .then(posts => res.json(posts)) + .catch(err => next(err)); +} + +function getById(req, res, next) { + postService.getById(req.params.id) + .then(post => post ? res.json(post) : res.sendStatus(404)) + .catch(err => next(err)); +} + +function update(req, res, next) { + postService.update(req.params.id, req.body) + .then(post => res.json(post)) + .catch(err => next(err)) +} + +function _delete(req, res, next) { + postService.delete(req.params.id) + .then(() => res.json({})) + .catch(err => next(err)) +} \ No newline at end of file diff --git a/blog-app/server/src/services/post.js b/blog-app/server/src/services/post.js new file mode 100644 index 0000000..c182891 --- /dev/null +++ b/blog-app/server/src/services/post.js @@ -0,0 +1,49 @@ +const config = require('src/config/keys'); +const jwt = require('jsonwebtoken'); +const bcrypt = require('bcryptjs'); + +// Post model +const Post = require('src/models/Post'); + +module.exports = { + create, + getAll, + getById, + update, + delete: _delete +}; + +async function create(postParam) { + const post = new Post(postParam); + + // save post + await post.save(); + + // return newly-created post + return post; +} + +async function getAll() { + return await Post.find({}, null, { sort: { date: -1 } }); +} + +async function getById(id) { + return await Post.findOne({ id }); +} + +async function update(id, postParam) { + const post = await Post.findOne({ id }); + + // copy postParam properties to post + Object.assign(post, postParam); + + // update existing post + await post.save(); + + // return updated post + return post; +} + +async function _delete(id) { + await Post.findOneAndRemove({ id }) +} \ No newline at end of file diff --git a/blog-app/server/src/services/user.js b/blog-app/server/src/services/user.js index 0d9d536..6a65ba3 100644 --- a/blog-app/server/src/services/user.js +++ b/blog-app/server/src/services/user.js @@ -26,11 +26,13 @@ async function authenticate({ username, password }) { } async function getAll() { - return await User.find(); + return await User.find() + .populate('posts'); } async function getById(id) { - return await User.findOne({ id }); + return await User.findOne({ id }) + .populate('posts'); } async function create(userParam) { From f2c496cdd73e3a82d9331d0bc865203b40423f32 Mon Sep 17 00:00:00 2001 From: "Renante D. Entera" Date: Thu, 29 Oct 2020 17:30:08 +0800 Subject: [PATCH 3/5] Course Project: Node JS - Refactor API (Updated) Updates include: > Refactor Models and Services > One-To-Many Relationships with MongoDB and Mongoose in Node/Express Fixing bugs: > `bug-schema-has-not-been-registered-for-model.png` = Schema hasn't been registered for model "User" --- blog-app/server/src/models/Post.js | 70 +++++++++---------- blog-app/server/src/models/User.js | 100 +++++++++++++-------------- blog-app/server/src/routes/users.js | 2 +- blog-app/server/src/services/post.js | 18 +++-- blog-app/server/src/services/user.js | 17 +++-- 5 files changed, 106 insertions(+), 101 deletions(-) diff --git a/blog-app/server/src/models/Post.js b/blog-app/server/src/models/Post.js index 4e1cf82..9823201 100644 --- a/blog-app/server/src/models/Post.js +++ b/blog-app/server/src/models/Post.js @@ -1,43 +1,39 @@ -const config = require('src/config/keys'); - -const mongoose = require('mongoose'), +module.exports = (connection) => { + const mongoose = require('mongoose'), Schema = mongoose.Schema, autoIncrement = require('mongoose-auto-increment'); -const connection = mongoose.createConnection(config.MONGO_URL); - -autoIncrement.initialize(connection); - -const schema = new Schema({ - id: { - type: Number, - required: true - }, - author: { - type: Schema.Types.ObjectId, - required: true, - ref: 'User' - }, - title: { - type: String, - require: true - }, - content: { - type: String, - require: true - }, - date: { - type: Date, - default: Date.now - } -}); + autoIncrement.initialize(connection); -schema.plugin(autoIncrement.plugin, { - model: 'Post', - field: 'id', - startAt: 1 -}); + const schema = new Schema({ + id: { + type: Number, + required: true + }, + author: { + type: Schema.Types.ObjectId, + required: true, + ref: 'User' + }, + title: { + type: String, + require: true + }, + content: { + type: String, + require: true + }, + date: { + type: Date, + default: Date.now + } + }); -const Post = connection.model('Post', schema); + schema.plugin(autoIncrement.plugin, { + model: 'Post', + field: 'id', + startAt: 1 + }); -module.exports = Post; + return connection.model('Post', schema); +} diff --git a/blog-app/server/src/models/User.js b/blog-app/server/src/models/User.js index 70866f6..910cdd7 100644 --- a/blog-app/server/src/models/User.js +++ b/blog-app/server/src/models/User.js @@ -1,58 +1,56 @@ -const config = require('src/config/keys'); -const mongoose = require('mongoose'), +module.exports = (connection) => { + const mongoose = require('mongoose'), Schema = mongoose.Schema, autoIncrement = require('mongoose-auto-increment'); -const connection = mongoose.createConnection(config.MONGO_URL); + autoIncrement.initialize(connection); -autoIncrement.initialize(connection); + const schema = new Schema({ + id: { + type: Number, + required: true + }, + username: { + type: String, + unique: true, + required: true + }, + hash: { + type: String, + required: true + }, + firstName: { + type: String, + required: true + }, + lastName: { + type: String, + required: true + }, + createdDate: { + type: Date, + default: Date.now + }, + posts: [{ + type: Schema.Types.ObjectId, + ref: 'Post' + }] + }); -const schema = new Schema({ - id: { - type: Number, - required: true - }, - username: { - type: String, - unique: true, - required: true - }, - hash: { - type: String, - required: true - }, - firstName: { - type: String, - required: true - }, - lastName: { - type: String, - required: true - }, - createdDate: { - type: Date, - default: Date.now - }, - posts: [{ - type: Schema.Types.ObjectId, - ref: 'Post' - }] -}); + schema.plugin(autoIncrement.plugin, { + model: 'User', + field: 'id', + startAt: 1 + }); -schema.plugin(autoIncrement.plugin, { - model: 'User', - field: 'id', - startAt: 1 -}); + schema.set('toJSON', { + virtuals: true, + versionKey: false, + transform: (doc, ret) => { + delete ret._id; + delete ret.hash; + } + }); -schema.set('toJSON', { - virtuals: true, - versionKey: false, - transform: (doc, ret) => { - delete ret._id; - delete ret.hash; - } -}); - -const User = connection.model('User', schema); -module.exports = User; \ No newline at end of file + return connection.model('User', schema); +} \ No newline at end of file diff --git a/blog-app/server/src/routes/users.js b/blog-app/server/src/routes/users.js index 52d6dcb..f23d47e 100644 --- a/blog-app/server/src/routes/users.js +++ b/blog-app/server/src/routes/users.js @@ -45,7 +45,7 @@ function getById(req, res, next) { function update(req, res, next) { userService.update(req.params.id, req.body) - .then(() => res.json({})) + .then(user => user ? res.json(user) : res.sendStatus(404)) .catch(err => next(err)) } diff --git a/blog-app/server/src/services/post.js b/blog-app/server/src/services/post.js index c182891..c7a3533 100644 --- a/blog-app/server/src/services/post.js +++ b/blog-app/server/src/services/post.js @@ -1,9 +1,10 @@ const config = require('src/config/keys'); -const jwt = require('jsonwebtoken'); -const bcrypt = require('bcryptjs'); +const mongoose = require('mongoose'); +const connection = mongoose.createConnection(config.MONGO_URL, { useNewUrlParser: true }); -// Post model -const Post = require('src/models/Post'); +// Models +const Post = require('src/models/Post')(connection); +const User = require('src/models/User')(connection); module.exports = { create, @@ -24,15 +25,18 @@ async function create(postParam) { } async function getAll() { - return await Post.find({}, null, { sort: { date: -1 } }); + return await Post.find({}, null, { sort: { date: -1 } }) + .populate({ path: 'author', select: 'firstName lastName role', model: User }); } async function getById(id) { - return await Post.findOne({ id }); + return await Post.findOne({ id }) + .populate({ path: 'author', select: 'firstName lastName role', model: User }); } async function update(id, postParam) { - const post = await Post.findOne({ id }); + const post = await Post.findOne({ id }) + .populate({ path: 'author', select: 'firstName lastName role', model: User }); // copy postParam properties to post Object.assign(post, postParam); diff --git a/blog-app/server/src/services/user.js b/blog-app/server/src/services/user.js index 6a65ba3..8cf5776 100644 --- a/blog-app/server/src/services/user.js +++ b/blog-app/server/src/services/user.js @@ -1,9 +1,12 @@ const config = require('src/config/keys'); +const mongoose = require('mongoose'); +const connection = mongoose.createConnection(config.MONGO_URL, { useNewUrlParser: true }); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); -// User model -const User = require('src/models/User'); +// Models +const Post = require('src/models/Post')(connection); +const User = require('src/models/User')(connection); module.exports = { authenticate, @@ -25,14 +28,14 @@ async function authenticate({ username, password }) { } } -async function getAll() { +async function getAll() { return await User.find() - .populate('posts'); + .populate({ path: 'posts', select: 'title content date', model: Post }); } async function getById(id) { return await User.findOne({ id }) - .populate('posts'); + .populate({ path: 'posts', select: 'title content date', model: Post }); } async function create(userParam) { @@ -69,7 +72,11 @@ async function update(id, userParam) { // copy userParam properties to user Object.assign(user, userParam); + // update existing user await user.save(); + + // return updated user + return user.populate({ path: 'posts', select: 'title content date', model: Post }); } async function _delete(id) { From dd12dfc472cd3006b90a15efeda5f119796f97bb Mon Sep 17 00:00:00 2001 From: "Renante D. Entera" Date: Fri, 30 Oct 2020 17:23:02 +0800 Subject: [PATCH 4/5] Course Project: Node JS - Refactor API (Updated - part 2) Updates include: > Add Post Reference to User's Post List When New Post is Saved > Populate Posts in Retrieving Users > Populate Author After Saving a Post Fixing bugs: > `bug-user-posts-not-populated.png` = User's posts were not populated --- blog-app/server/src/services/post.js | 8 +++++++- blog-app/server/src/services/user.js | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/blog-app/server/src/services/post.js b/blog-app/server/src/services/post.js index c7a3533..052e804 100644 --- a/blog-app/server/src/services/post.js +++ b/blog-app/server/src/services/post.js @@ -16,12 +16,18 @@ module.exports = { async function create(postParam) { const post = new Post(postParam); + const user = await User.findById(postParam.author); // save post await post.save(); + // update user's post list + user.posts = user.posts.concat(post._id); + await user.save(); + // return newly-created post - return post; + return await Post.findById(post._id) + .populate({ path: 'author', select: 'firstName lastName role', model: User }); } async function getAll() { diff --git a/blog-app/server/src/services/user.js b/blog-app/server/src/services/user.js index 8cf5776..7964b10 100644 --- a/blog-app/server/src/services/user.js +++ b/blog-app/server/src/services/user.js @@ -30,12 +30,12 @@ async function authenticate({ username, password }) { async function getAll() { return await User.find() - .populate({ path: 'posts', select: 'title content date', model: Post }); + .populate({ path: 'posts', select: 'id title content date', model: Post }); } async function getById(id) { return await User.findOne({ id }) - .populate({ path: 'posts', select: 'title content date', model: Post }); + .populate({ path: 'posts', select: 'id title content date', model: Post }); } async function create(userParam) { @@ -76,7 +76,7 @@ async function update(id, userParam) { await user.save(); // return updated user - return user.populate({ path: 'posts', select: 'title content date', model: Post }); + return user.populate({ path: 'posts', select: 'id title content date', model: Post }); } async function _delete(id) { From fcbc7cb49dcb8bbc7357a143aadf640997e4abd6 Mon Sep 17 00:00:00 2001 From: "Renante D. Entera" Date: Sat, 31 Oct 2020 18:35:00 +0800 Subject: [PATCH 5/5] Course Project: Additional Features and Enhancements Updates include: > Node JS - Return Column `_id` When Retrieving User > Node JS - Create API Endpoint to Retrieve My Posts > Angular - Add Column "No. of Posts" in List of Users > Angular - Replace "AuthenticationService" with "AccountService" > Angular - Remove "UserService" > Angular - Pass Author Reference When Creating a Post > Angular - Add Column "Author" in List of Posts > Angular - Create "My Posts" Page Fixing bugs: > `bug-post-validation-failed-add-post.png` = Post validation failed: author: Path `author` is required --- .../client/src/app/_helpers/auth.guard.ts | 6 +-- .../src/app/_helpers/error.interceptor.ts | 6 +-- .../src/app/_helpers/jwt.interceptor.ts | 6 +-- blog-app/client/src/app/_models/post.ts | 1 + blog-app/client/src/app/_models/user.ts | 1 + .../src/app/_services/account.service.ts | 4 ++ .../app/_services/authentication.service.ts | 43 ------------------- blog-app/client/src/app/_services/index.ts | 4 +- .../client/src/app/_services/post.service.ts | 7 ++- .../client/src/app/_services/user.service.ts | 18 -------- .../client/src/app/admin/admin.component.ts | 6 +-- blog-app/client/src/app/app-routing.module.ts | 5 +++ blog-app/client/src/app/app.component.html | 3 +- blog-app/client/src/app/app.component.ts | 9 ++-- .../client/src/app/home/home.component.ts | 9 ++-- .../src/app/posts/add-edit.component.ts | 28 +++++++++--- .../client/src/app/posts/list.component.html | 8 ++-- .../client/src/app/posts/list.component.ts | 23 ++++++++-- .../client/src/app/users/list.component.html | 4 +- blog-app/server/src/models/User.js | 1 - blog-app/server/src/routes/users.js | 13 ++++++ blog-app/server/src/services/post.js | 8 +++- 22 files changed, 107 insertions(+), 106 deletions(-) delete mode 100644 blog-app/client/src/app/_services/authentication.service.ts delete mode 100644 blog-app/client/src/app/_services/user.service.ts diff --git a/blog-app/client/src/app/_helpers/auth.guard.ts b/blog-app/client/src/app/_helpers/auth.guard.ts index bd879e8..0986c6c 100644 --- a/blog-app/client/src/app/_helpers/auth.guard.ts +++ b/blog-app/client/src/app/_helpers/auth.guard.ts @@ -1,17 +1,17 @@ import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { AuthenticationService } from '@app/_services'; +import { AccountService } from '@app/_services'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor( private router: Router, - private authenticationService: AuthenticationService + private accountService: AccountService ) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { - const user = this.authenticationService.userValue; + 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) { diff --git a/blog-app/client/src/app/_helpers/error.interceptor.ts b/blog-app/client/src/app/_helpers/error.interceptor.ts index 92855b7..28e77ef 100644 --- a/blog-app/client/src/app/_helpers/error.interceptor.ts +++ b/blog-app/client/src/app/_helpers/error.interceptor.ts @@ -3,17 +3,17 @@ import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/c import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; -import { AuthenticationService } from '@app/_services'; +import { AccountService } from '@app/_services'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { - constructor(private authenticationService: AuthenticationService) { } + constructor(private accountService: AccountService) { } intercept(request: HttpRequest, next: HttpHandler): Observable> { return next.handle(request).pipe(catchError(err => { if ([401, 403].indexOf(err.status) !== -1) { // auto logout if 401 Unauthorized or 403 Forbidden response returned from api - this.authenticationService.logout(); + this.accountService.logout(); } const error = err.error.message || err.statusText; diff --git a/blog-app/client/src/app/_helpers/jwt.interceptor.ts b/blog-app/client/src/app/_helpers/jwt.interceptor.ts index be1fd55..1948bd2 100644 --- a/blog-app/client/src/app/_helpers/jwt.interceptor.ts +++ b/blog-app/client/src/app/_helpers/jwt.interceptor.ts @@ -3,15 +3,15 @@ import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/c import { Observable } from 'rxjs'; import { environment } from '@environments/environment'; -import { AuthenticationService } from '@app/_services'; +import { AccountService } from '@app/_services'; @Injectable() export class JwtInterceptor implements HttpInterceptor { - constructor(private authenticationService: AuthenticationService) { } + constructor(private accountService: AccountService) { } intercept(request: HttpRequest, next: HttpHandler): Observable> { // add auth header with jwt if user is logged in and request is to api url - const user = this.authenticationService.userValue; + const user = this.accountService.userValue; const isLoggedIn = user && user.token; const isApiUrl = request.url.startsWith(environment.apiUrl); if (isLoggedIn && isApiUrl) { diff --git a/blog-app/client/src/app/_models/post.ts b/blog-app/client/src/app/_models/post.ts index 8c3d527..b46fac7 100644 --- a/blog-app/client/src/app/_models/post.ts +++ b/blog-app/client/src/app/_models/post.ts @@ -1,5 +1,6 @@ export class Post { id: string; + author: string; title: string; content: string; date: Date; diff --git a/blog-app/client/src/app/_models/user.ts b/blog-app/client/src/app/_models/user.ts index d109af7..5f626dc 100644 --- a/blog-app/client/src/app/_models/user.ts +++ b/blog-app/client/src/app/_models/user.ts @@ -1,6 +1,7 @@ import { Role } from "./role"; export class User { + _id: string; id: string; username: string; password: string; diff --git a/blog-app/client/src/app/_services/account.service.ts b/blog-app/client/src/app/_services/account.service.ts index 05056a2..839ede3 100644 --- a/blog-app/client/src/app/_services/account.service.ts +++ b/blog-app/client/src/app/_services/account.service.ts @@ -53,6 +53,10 @@ export class AccountService { return this.http.get(`${environment.apiUrl}/users/${id}`); } + getCurrentUser() { + return this.http.get(`${environment.apiUrl}/users/current`); + } + update(id, params) { return this.http.put(`${environment.apiUrl}/users/${id}`, params) .pipe(map(x => { diff --git a/blog-app/client/src/app/_services/authentication.service.ts b/blog-app/client/src/app/_services/authentication.service.ts deleted file mode 100644 index 5f2f722..0000000 --- a/blog-app/client/src/app/_services/authentication.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; -import { HttpClient } from '@angular/common/http'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import { environment } from '@environments/environment'; -import { User } from '@app/_models'; - -@Injectable({ providedIn: 'root' }) -export class AuthenticationService { - private userSubject: BehaviorSubject; - public user: Observable; - - constructor( - private router: Router, - private http: HttpClient - ) { - this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user'))); - this.user = this.userSubject.asObservable(); - } - - public get userValue(): User { - return this.userSubject.value; - } - - login(username: string, password: string) { - return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, password }) - .pipe(map(user => { - // store user details and jwt token in local storage to keep user logged in between page refreshes - localStorage.setItem('user', JSON.stringify(user)); - this.userSubject.next(user); - return user; - })); - } - - logout() { - // remove user from local storage to log user out - localStorage.removeItem('user'); - this.userSubject.next(null); - this.router.navigate(['/account/login']); - } -} \ No newline at end of file diff --git a/blog-app/client/src/app/_services/index.ts b/blog-app/client/src/app/_services/index.ts index 1783b82..27b76a5 100644 --- a/blog-app/client/src/app/_services/index.ts +++ b/blog-app/client/src/app/_services/index.ts @@ -1,5 +1,3 @@ export * from './account.service'; export * from './alert.service'; -export * from './authentication.service'; -export * from './post.service'; -export * from './user.service'; \ No newline at end of file +export * from './post.service'; \ No newline at end of file diff --git a/blog-app/client/src/app/_services/post.service.ts b/blog-app/client/src/app/_services/post.service.ts index e5bb94c..febec21 100644 --- a/blog-app/client/src/app/_services/post.service.ts +++ b/blog-app/client/src/app/_services/post.service.ts @@ -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 { @@ -22,6 +21,10 @@ export class PostService { return this.http.get(`${environment.apiUrl}/posts/${id}`); } + getMyPosts() { + return this.http.get(`${environment.apiUrl}/users/my-posts`); + } + update(id, params) { return this.http.put(`${environment.apiUrl}/posts/${id}`, params) .pipe(map(x => { diff --git a/blog-app/client/src/app/_services/user.service.ts b/blog-app/client/src/app/_services/user.service.ts deleted file mode 100644 index aee90b9..0000000 --- a/blog-app/client/src/app/_services/user.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; - -import { environment } from '@environments/environment'; -import { User } from '@app/_models'; - -@Injectable({ providedIn: 'root' }) -export class UserService { - constructor(private http: HttpClient) { } - - getAll() { - return this.http.get(`${environment.apiUrl}/users`); - } - - getById(id: string) { - return this.http.get(`${environment.apiUrl}/users/${id}`); - } -} \ No newline at end of file diff --git a/blog-app/client/src/app/admin/admin.component.ts b/blog-app/client/src/app/admin/admin.component.ts index b913909..4138593 100644 --- a/blog-app/client/src/app/admin/admin.component.ts +++ b/blog-app/client/src/app/admin/admin.component.ts @@ -2,18 +2,18 @@ import { Component, OnInit } from '@angular/core'; import { first } from 'rxjs/operators'; import { User } from '@app/_models'; -import { UserService } from '@app/_services'; +import { AccountService } from '@app/_services'; @Component({ templateUrl: 'admin.component.html' }) export class AdminComponent implements OnInit { loading = false; users: User[] = []; - constructor(private userService: UserService) { } + constructor(private accountService: AccountService) { } ngOnInit() { this.loading = true; - this.userService.getAll().pipe(first()).subscribe(users => { + this.accountService.getAll().pipe(first()).subscribe(users => { this.loading = false; this.users = users; }); diff --git a/blog-app/client/src/app/app-routing.module.ts b/blog-app/client/src/app/app-routing.module.ts index d1546c8..3846069 100644 --- a/blog-app/client/src/app/app-routing.module.ts +++ b/blog-app/client/src/app/app-routing.module.ts @@ -22,6 +22,11 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { roles: [Role.Admin] } }, + { + path: 'my-posts', + loadChildren: postsModule, + canActivate: [AuthGuard] + }, { path: 'posts', loadChildren: postsModule, diff --git a/blog-app/client/src/app/app.component.html b/blog-app/client/src/app/app.component.html index 3e1d939..3e23327 100644 --- a/blog-app/client/src/app/app.component.html +++ b/blog-app/client/src/app/app.component.html @@ -2,8 +2,9 @@ diff --git a/blog-app/client/src/app/app.component.ts b/blog-app/client/src/app/app.component.ts index 49dbef5..8fc637b 100644 --- a/blog-app/client/src/app/app.component.ts +++ b/blog-app/client/src/app/app.component.ts @@ -1,15 +1,14 @@ import { Component } from '@angular/core'; -import { Router } from '@angular/router'; -import { AuthenticationService } from './_services'; +import { AccountService } from './_services'; import { User, Role } from './_models'; @Component({ selector: 'app', templateUrl: 'app.component.html' }) export class AppComponent { user: User; - constructor(private authenticationService: AuthenticationService) { - this.authenticationService.user.subscribe(x => this.user = x); + constructor(private accountService: AccountService) { + this.accountService.user.subscribe(x => this.user = x); } get isAdmin() { @@ -17,6 +16,6 @@ export class AppComponent { } logout() { - this.authenticationService.logout(); + this.accountService.logout(); } } \ No newline at end of file diff --git a/blog-app/client/src/app/home/home.component.ts b/blog-app/client/src/app/home/home.component.ts index 54343ed..1b8a9ee 100644 --- a/blog-app/client/src/app/home/home.component.ts +++ b/blog-app/client/src/app/home/home.component.ts @@ -2,7 +2,7 @@ import { first } from 'rxjs/operators'; import { User } from '@app/_models'; -import { UserService, AuthenticationService } from '@app/_services'; +import { AccountService } from '@app/_services'; @Component({ templateUrl: 'home.component.html' }) export class HomeComponent { @@ -11,15 +11,14 @@ export class HomeComponent { userFromApi: User; constructor( - private userService: UserService, - private authenticationService: AuthenticationService + private accountService: AccountService ) { - this.user = this.authenticationService.userValue; + this.user = this.accountService.userValue; } ngOnInit() { this.loading = true; - this.userService.getById(this.user.id).pipe(first()).subscribe(user => { + this.accountService.getById(this.user.id).pipe(first()).subscribe(user => { this.loading = false; this.userFromApi = user; }); diff --git a/blog-app/client/src/app/posts/add-edit.component.ts b/blog-app/client/src/app/posts/add-edit.component.ts index 78d944c..afc423b 100644 --- a/blog-app/client/src/app/posts/add-edit.component.ts +++ b/blog-app/client/src/app/posts/add-edit.component.ts @@ -3,7 +3,8 @@ import { Router, ActivatedRoute } from '@angular/router'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { first } from 'rxjs/operators'; -import { PostService, AlertService } from '@app/_services'; +import { User } from '@app/_models'; +import { AccountService, AlertService, PostService } from '@app/_services'; @Component({ templateUrl: 'add-edit.component.html' }) export class AddEditComponent implements OnInit { @@ -12,23 +13,36 @@ export class AddEditComponent implements OnInit { isAddMode: boolean; loading = false; submitted = false; + user: User; constructor( private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, + private accountService: AccountService, + private alertService: AlertService, private postService: PostService, - private alertService: AlertService - ) {} + ) { + this.user = this.accountService.userValue; + } ngOnInit() { this.id = this.route.snapshot.params['id']; this.isAddMode = !this.id; - this.form = this.formBuilder.group({ - title: ['', Validators.required], - content: ['', Validators.required] - }); + // Pass Author Reference When Creating a Post + if (this.isAddMode) { + this.form = this.formBuilder.group({ + author: this.user._id, + title: ['', Validators.required], + content: ['', Validators.required] + }); + } else { + this.form = this.formBuilder.group({ + title: ['', Validators.required], + content: ['', Validators.required] + }); + } if (!this.isAddMode) { this.postService.getById(this.id) diff --git a/blog-app/client/src/app/posts/list.component.html b/blog-app/client/src/app/posts/list.component.html index 3098418..e227e99 100644 --- a/blog-app/client/src/app/posts/list.component.html +++ b/blog-app/client/src/app/posts/list.component.html @@ -1,11 +1,12 @@ -

Posts

+

{{my}}Posts

Add Post - - + + + @@ -13,6 +14,7 @@ + - + + @@ -14,6 +15,7 @@ +
TitleContentDateContentAuthorDate
{{post.title}} {{post.content}}{{post.author.firstName}} {{post.author.lastName}} {{post.date | date:short}} Edit diff --git a/blog-app/client/src/app/posts/list.component.ts b/blog-app/client/src/app/posts/list.component.ts index 34a3fa5..d060774 100644 --- a/blog-app/client/src/app/posts/list.component.ts +++ b/blog-app/client/src/app/posts/list.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; import { first } from 'rxjs/operators'; import { PostService } from '@app/_services'; @@ -6,13 +7,27 @@ import { PostService } from '@app/_services'; @Component({ templateUrl: 'list.component.html' }) export class ListComponent implements OnInit { posts = null; + my = ''; - constructor(private postService: PostService) {} + constructor( + private postService: PostService, + private router: Router + ) {} ngOnInit() { - this.postService.getAll() - .pipe(first()) - .subscribe(posts => this.posts = posts); + if (this.router.url == '/my-posts') { + this.my = 'My '; + + this.postService.getMyPosts() + .pipe(first()) + .subscribe(posts => this.posts = posts); + } else { + this.my = 'All '; + + this.postService.getAll() + .pipe(first()) + .subscribe(posts => this.posts = posts); + } } deletePost(id: string) { diff --git a/blog-app/client/src/app/users/list.component.html b/blog-app/client/src/app/users/list.component.html index 1781e68..85da14d 100644 --- a/blog-app/client/src/app/users/list.component.html +++ b/blog-app/client/src/app/users/list.component.html @@ -5,7 +5,8 @@
First Name Last NameUsernameUsername# of Posts
{{user.firstName}} {{user.lastName}} {{user.username}}{{user.posts.length}} Edit