From baeded4a7adfe5aa163e1fc1375e95f73a8080bd Mon Sep 17 00:00:00 2001 From: vigdorov Date: Sun, 26 Jul 2020 16:42:00 +0300 Subject: [PATCH] users api --- package-lock.json | 29 +++++++++++ package.json | 1 + src/consts.ts | 2 +- src/users/users.contoller.ts | 68 ++++++++++++++++++++++++-- src/users/users.schema.ts | 77 +++++++++++++++++++++++++---- src/users/users.service.ts | 94 +++++++++++++++++++++++++++++++++--- 6 files changed, 250 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c3a8a0..e6d8356 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1847,6 +1847,15 @@ "@types/node": "*" } }, + "@types/bson": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.2.tgz", + "integrity": "sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -1959,6 +1968,26 @@ "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", "dev": true }, + "@types/mongodb": { + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.25.tgz", + "integrity": "sha512-2H/Owt+pHCl9YmBOYnXc3VdnxejJEjVdH+QCWL5ZAfPehEn3evygKBX3/vKRv7aTwfNbUd0E5vjJdQklH/9a6w==", + "dev": true, + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "@types/mongoose": { + "version": "5.7.32", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.7.32.tgz", + "integrity": "sha512-o1qijffQipTtYMJEYF8BOd+D8fy6ZGtGKP654udSEp6wysU3r1O2T8wHSP9QIC//QwQgKQGolu2y9vc9KXaq4w==", + "dev": true, + "requires": { + "@types/mongodb": "*", + "@types/node": "*" + } + }, "@types/node": { "version": "13.13.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz", diff --git a/package.json b/package.json index d15600d..107e75c 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@nestjs/testing": "^7.0.0", "@types/express": "^4.17.3", "@types/jest": "25.2.3", + "@types/mongoose": "^5.7.32", "@types/node": "^13.9.1", "@types/supertest": "^2.0.8", "@typescript-eslint/eslint-plugin": "3.0.2", diff --git a/src/consts.ts b/src/consts.ts index bf565a3..d510306 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -8,4 +8,4 @@ export const ALLOW_ORIGIN_ALL: [string, string] = ['Access-Control-Allow-Origin' export const ALLOW_CREDENTIALS: [string, string] = ['Access-Control-Allow-Credentials', 'true']; export const CONTENT_LENGTH: [string, string] = ['Content-Length', '0']; export const ALLOW_METHOD: [string, string] = ['Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE']; -export const ALLOW_HEADERS: [string, string] = ['Access-Control-Allow-Headers', 'Version, Authorization, Content-Type, Api-Name']; \ No newline at end of file +export const ALLOW_HEADERS: [string, string] = ['Access-Control-Allow-Headers', 'Version, Authorization, Content-Type']; diff --git a/src/users/users.contoller.ts b/src/users/users.contoller.ts index 468883d..4802abe 100644 --- a/src/users/users.contoller.ts +++ b/src/users/users.contoller.ts @@ -1,9 +1,10 @@ -import {Controller, Get, Req, Post, Options, Header, Delete, HttpCode, Put, UseInterceptors, All} from '@nestjs/common'; +import {Controller, Get, Req, Post, Options, Header, Delete, HttpCode, Put} from '@nestjs/common'; import {ApiResponse, ApiTags, ApiParam, ApiBody} from '@nestjs/swagger'; import {ALLOW_ORIGIN_ALL, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS, USERS_CONTROLLER} from '../consts'; import {UserService} from './users.service'; -import {UserRequest} from './users.schema'; +import {UserResponse, CreateUserRequest, UpdateUserRequest} from './users.schema'; +import {Request} from 'express'; @Controller(USERS_CONTROLLER) @ApiTags(USERS_CONTROLLER) @@ -17,12 +18,71 @@ export class UsersController { @ApiResponse({ status: 200, description: 'Список всех пользователей', - type: [UserRequest] + type: [UserResponse] }) - async findAll(): Promise { + async findAll(): Promise { return this.userService.findAll(); } + @Get(':login') + @Header(...ALLOW_ORIGIN_ALL) + @ApiResponse({ + status: 200, + description: 'Получить пользователя по логину' + }) + @ApiParam({ + name: 'login', + description: 'Логин пользователя', + }) + async findOne(@Req() request: Request<{login: string}>): Promise { + return await this.userService.findOne(request.params.login); + } + + @Post() + @Header(...ALLOW_ORIGIN_ALL) + @ApiResponse({ + status: 201, + description: 'Создает пользователя в системе', + type: UserResponse, + }) + @ApiBody({ + type: CreateUserRequest, + description: 'Принимает объект для создания пользователя' + }) + async createUser(@Req() request: Request): Promise { + return await this.userService.create(request.body); + } + + @Put() + @Header(...ALLOW_ORIGIN_ALL) + @ApiResponse({ + status: 200, + description: 'Обновляет данные пользователя', + type: UpdateUserRequest + }) + @ApiBody({ + type: UpdateUserRequest, + description: 'Принимает объект для обновления данных пользователя' + }) + async updateUser(@Req() request: Request): Promise { + return await this.userService.update(request.body); + } + + @Delete(':login') + @Header(...ALLOW_ORIGIN_ALL) + @ApiResponse({ + status: 200, + description: 'Удаляет пользователя', + type: UpdateUserRequest, + }) + @ApiParam({ + name: 'login', + description: 'Логин пользователя', + }) + async removeUser(@Req() request: Request<{login: string}>): Promise { + return await this.userService.removeOne(request.params.login); + } + @Options() @Header(...ALLOW_ORIGIN_ALL) @Header(...ALLOW_METHOD) diff --git a/src/users/users.schema.ts b/src/users/users.schema.ts index c6a456f..954408f 100644 --- a/src/users/users.schema.ts +++ b/src/users/users.schema.ts @@ -1,13 +1,13 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { Document } from 'mongoose'; import {ApiProperty} from '@nestjs/swagger'; +import { Document } from 'mongoose'; interface Token { token: string; expired_at: string; } -export class UserRequest { +export class CreateUserRequest { @ApiProperty() login: string; @@ -15,7 +15,46 @@ export class UserRequest { avatar: string; @ApiProperty() - is_admin: string; + password: string; +} + +export class UpdateUserRequest { + @ApiProperty() + login: string; + + @ApiProperty() + avatar: string; +} + +export class UserResponse { + @ApiProperty() + login: string; + + @ApiProperty() + avatar: string; + + @ApiProperty() + is_admin: boolean; +} + +export class UserModel { + @ApiProperty() + login: string; + + @ApiProperty() + avatar: string; + + @ApiProperty() + password: string; + + @ApiProperty() + is_admin: boolean; + + @ApiProperty() + access_token: Token[]; + + @ApiProperty() + refresh_token: Token[]; } @Schema() @@ -44,20 +83,40 @@ export class User extends Document { is_admin: boolean; @Prop({ - type: { + type: [{ token: String, expired_at: String, - } + }] }) - access_token: Token; + access_token: Token[]; @Prop({ - type: { + type: [{ token: String, expired_at: String, - } + }] }) - refresh_token: Token; + refresh_token: Token[]; } +@Schema() +export class UserUpdate extends Document { + @Prop({ + required: true, + type: String, + }) + login: string; + + @Prop({ + type: String, + }) + avatar: string; + + @Prop({ + type: Boolean, + }) + is_admin: boolean; +} + +export const UserUpdateSchema = SchemaFactory.createForClass(UserUpdate); export const UserSchema = SchemaFactory.createForClass(User); diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 2ec0533..a7bf6ba 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,20 +1,100 @@ -import {Model, Connection} from 'mongoose'; -import {Injectable} from '@nestjs/common'; +import {Model, Connection, Document} from 'mongoose'; +import {Injectable, NotFoundException, BadGatewayException} from '@nestjs/common'; import {InjectConnection} from '@nestjs/mongoose'; import {DB_NAME, USERS_CONTROLLER} from 'src/consts'; -import {User, UserSchema} from './users.schema'; +import {User, UserSchema, CreateUserRequest, UserResponse, UserModel, UpdateUserRequest} from './users.schema'; + +const validateModel = async (user: Document) => { + try { + await user.validate(); + } catch (e) { + throw new BadGatewayException(e); + } +}; + +const prepareUserToUserModel = (user: User): UserModel => ({ + login: user.login, + avatar: user.avatar, + password: user.password, + is_admin: user.is_admin, + access_token: user.access_token, + refresh_token: user.refresh_token, +}); + +const prepareUserToUserResponse = (user: User): UserResponse => ({ + login: user.login, + avatar: user.avatar, + is_admin: user.is_admin, +}); @Injectable() export class UserService { constructor( @InjectConnection(DB_NAME) private dbConnection: Connection, ) {} - - get userModel(): Model { + + userModel(): Model { return this.dbConnection.model(USERS_CONTROLLER, UserSchema); } - findAll(): any { - return this.userModel.find().exec(); + async findUser(login: string): Promise { + const user = await this.userModel().findOne({login}); + return user ? prepareUserToUserModel(user) : user; + } + + async findOne(login: string): Promise { + const user = await this.userModel().findOne({login}); + if (!user) { + throw new NotFoundException(`Пользователь с логином ${login} не найден`); + } + return prepareUserToUserResponse(user); + } + + async findAll(): Promise { + const users = await this.userModel().find().exec(); + return users.map(prepareUserToUserResponse); + } + + async create(user: CreateUserRequest): Promise { + const searchUser = await this.findUser(user.login); + + if (searchUser) { + throw new NotFoundException(`Пользователь с логином ${user.login} уже существует`); + } + + const Model = await this.userModel(); + const createUser = new Model(user); + await validateModel(createUser); + + const savedUser = await createUser.save(); + + return prepareUserToUserResponse(savedUser); + } + + async update(user: UpdateUserRequest): Promise { + const searchUser = await this.userModel().findOne({login: user.login}); + + const Model = await this.userModel(); + const updateUser = new Model({ + ...user, + password: searchUser.password, + }); + await validateModel(updateUser); + + await searchUser.updateOne({ + ...{ + avatar: user.avatar, + }, + }); + + return prepareUserToUserResponse(updateUser); + } + + async removeOne(login: string): Promise { + const searchUser = await this.userModel().findOne({login}); + + await this.userModel().deleteOne({login}); + + return prepareUserToUserResponse(searchUser); } } \ No newline at end of file