diff --git a/src/client/app/cms/cms-routing.module.ts b/src/client/app/cms/cms-routing.module.ts index 13db19a..95f9794 100644 --- a/src/client/app/cms/cms-routing.module.ts +++ b/src/client/app/cms/cms-routing.module.ts @@ -10,7 +10,12 @@ const routes: Routes = [ path: '', component: CmsComponent, canActivate: [CurrentUserGuard], - children: [] + children: [ + { + path: 'profile', + loadChildren: '../profile/profile.module#ProfileModule' + } + ] } ]; diff --git a/src/client/app/cms/cms-sidenav/cms-sidenav.component.html b/src/client/app/cms/cms-sidenav/cms-sidenav.component.html index 4e03837..7225ac9 100644 --- a/src/client/app/cms/cms-sidenav/cms-sidenav.component.html +++ b/src/client/app/cms/cms-sidenav/cms-sidenav.component.html @@ -4,5 +4,6 @@ Home + Profile Logout \ No newline at end of file diff --git a/src/client/app/core/services/user.service.ts b/src/client/app/core/services/user.service.ts index c4a02e4..3397e68 100644 --- a/src/client/app/core/services/user.service.ts +++ b/src/client/app/core/services/user.service.ts @@ -58,4 +58,12 @@ export class UserService { this.apiService.setAccessToken(null); }); } + + public updateProfile(data: User): Observable { + return this.apiService.put(`/users/profile`, data) + .do((user: User) => { + console.log('updated current user', user); + this.currentUserSource.next(user); + }); + } } diff --git a/src/client/app/profile/profile-routing.module.ts b/src/client/app/profile/profile-routing.module.ts new file mode 100644 index 0000000..0df748d --- /dev/null +++ b/src/client/app/profile/profile-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { ProfileComponent } from './profile.component'; + +const routes: Routes = [ + { path: '', component: ProfileComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ProfileRoutingModule { } + +export const routedComponents = [ProfileComponent]; diff --git a/src/client/app/profile/profile.component.html b/src/client/app/profile/profile.component.html new file mode 100644 index 0000000..3ffb4ed --- /dev/null +++ b/src/client/app/profile/profile.component.html @@ -0,0 +1,7 @@ +

Profile

+
+ + + + +
diff --git a/src/client/app/profile/profile.component.scss b/src/client/app/profile/profile.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/client/app/profile/profile.component.ts b/src/client/app/profile/profile.component.ts new file mode 100644 index 0000000..49c55ce --- /dev/null +++ b/src/client/app/profile/profile.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; + +import { Role, User } from '../../../common/entities'; + +import { UserService } from '../core'; + +@Component({ + selector: 'profile', + templateUrl: 'profile.component.html', + styleUrls: ['profile.component.scss'] +}) + +export class ProfileComponent implements OnInit { + + public user: User; + public profileForm: FormGroup; + + constructor( + private formBuilder: FormBuilder, + private userService: UserService + ) { } + + // interface methods + public ngOnInit() { + this.loadProfile(); + this.buildProfileForm(); + this.patchProfileFormValue(this.user); + } + + // event methods + public onClickUpdate() { + const data = this.profileForm.getRawValue(); + this.updateProfile(data); + } + + private loadProfile() { + this.userService.currentUser$.subscribe((user: User) => { + this.user = user; + }); + } + + private buildProfileForm() { + this.profileForm = this.formBuilder.group({ + email: ['', Validators.required] + }); + } + + private patchProfileFormValue(user: User) { + this.profileForm.patchValue({ + email: user.email + }); + } + + private updateProfile(data: User) { + this.userService + .updateProfile(data) + .subscribe((user: User) => { + this.user = user; + this.patchProfileFormValue(this.user); + }, (error: Error) => { + console.log(error); + }); + } +} diff --git a/src/client/app/profile/profile.module.ts b/src/client/app/profile/profile.module.ts new file mode 100644 index 0000000..149b2f2 --- /dev/null +++ b/src/client/app/profile/profile.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared'; + +import { ProfileRoutingModule, routedComponents } from './profile-routing.module'; + +@NgModule({ + imports: [ + SharedModule, + ProfileRoutingModule + ], + exports: [], + declarations: [ + routedComponents + ], + providers: [] +}) +export class ProfileModule { } diff --git a/src/client/app/shared/shared.module.ts b/src/client/app/shared/shared.module.ts index 1eb7f17..08a9181 100644 --- a/src/client/app/shared/shared.module.ts +++ b/src/client/app/shared/shared.module.ts @@ -1,5 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MaterialModule } from '../material'; @@ -12,10 +13,14 @@ import { ListComponent } from './list/list.component'; @NgModule({ imports: [ CommonModule, + FormsModule, + ReactiveFormsModule, MaterialModule ], exports: [ CommonModule, + FormsModule, + ReactiveFormsModule, MaterialModule, ListComponent ], diff --git a/src/server/app/users/user.controller.test.ts b/src/server/app/users/user.controller.test.ts index 1a027c6..f9c7d94 100644 --- a/src/server/app/users/user.controller.test.ts +++ b/src/server/app/users/user.controller.test.ts @@ -105,4 +105,20 @@ describe('UserController', () => { .expect(200); }); }); + + describe.only('updateProfileById', async () => { + it('should update current user, /api/users/profile', async () => { + const data = await userService.login({ + email: `admin@test.com`, + password: `test` + }); + const response = await server + .put('/api/users/profile') + .set('x-access-token', data.token) + .send({ + email: 'updated-admin@test.com' + }); + expect(response.body).to.have.property('email', 'updated-admin@test.com'); + }); + }); }); diff --git a/src/server/app/users/user.controller.ts b/src/server/app/users/user.controller.ts index d587055..0201ab6 100644 --- a/src/server/app/users/user.controller.ts +++ b/src/server/app/users/user.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Post, UseGuards, Put } from '@nestjs/common'; import { AccessTokenGuard, CurrentUser } from '../core'; @@ -30,4 +30,14 @@ export class UserController { public getUsers() { return this.userService.getUsers(); } + + @Put('profile') + @UseGuards(AccessTokenGuard) + public async updateProfile( + @CurrentUser() currentUser: User, + @Body() user: User + ): Promise { + await this.userService.updateProfileById(currentUser.id, user); + return this.userService.getUserById(currentUser.id); + } } diff --git a/src/server/app/users/user.repository.ts b/src/server/app/users/user.repository.ts index fd8f9e3..3338725 100644 --- a/src/server/app/users/user.repository.ts +++ b/src/server/app/users/user.repository.ts @@ -22,8 +22,14 @@ export class UserRepository extends Repository { public async getUserByEmail(email: string): Promise { return this.findOne({ - where: { email } + where: { email }, + relations: ['roles'] }); } + public updateUserById(id: number, data: User): Promise { + delete data.created; + return this.updateById(id, data); + } + } diff --git a/src/server/app/users/user.service.ts b/src/server/app/users/user.service.ts index d738b46..9d5afae 100644 --- a/src/server/app/users/user.service.ts +++ b/src/server/app/users/user.service.ts @@ -1,4 +1,5 @@ import { Component, Inject, UnauthorizedException } from '@nestjs/common'; +import { classToPlain } from 'class-transformer'; import { BcryptService, JsonWebTokenService } from '../core'; import { User } from './user.entity'; @@ -22,11 +23,7 @@ export class UserService { if (!isValidPassword) { throw new UnauthorizedException(); } - const token = this.jwtService.sign({ - id: user.id, - email: user.email, - roles: user.roles - }); + const token = this.jwtService.sign(classToPlain(user)); return { user, token }; } @@ -39,4 +36,9 @@ export class UserService { public async getUsers() { return this.userRepository.getUsers({}); } + + public updateProfileById(id: number, data: User) { + delete data.password; + return this.userRepository.updateUserById(id, data); + } }