├── nest-cli.json ├── tsconfig.build.json ├── src ├── users │ ├── dto │ │ ├── ban-user.dto.ts │ │ ├── add-role.dto.ts │ │ └── create-user.dto.ts │ ├── users.module.ts │ ├── users.model.ts │ ├── users.controller.ts │ └── users.service.ts ├── roles │ ├── dto │ │ └── create-role.dto.ts │ ├── roles.controller.ts │ ├── roles.module.ts │ ├── roles.service.ts │ ├── user-roles.model.ts │ └── roles.model.ts ├── posts │ ├── dto │ │ └── create-post.dto.ts │ ├── posts.controller.ts │ ├── posts.module.ts │ ├── posts.service.ts │ └── posts.model.ts ├── auth │ ├── roles-auth.decorator.ts │ ├── auth.controller.ts │ ├── auth.module.ts │ ├── jwt-auth.guard.ts │ ├── roles.guard.ts │ └── auth.service.ts ├── files │ ├── files.module.ts │ └── files.service.ts ├── exceptions │ └── validation.exception.ts ├── pipes │ └── validation.pipe.ts ├── main.ts └── app.module.ts ├── .production.env ├── .development.env ├── README.md ├── Dockerfile ├── tsconfig.json ├── .gitignore ├── docker-compose.yml └── package.json /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/users/dto/ban-user.dto.ts: -------------------------------------------------------------------------------- 1 | export class BanUserDto { 2 | readonly userId: number; 3 | readonly banReason: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/roles/dto/create-role.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateRoleDto { 2 | readonly value: string; 3 | readonly description: string; 4 | } 5 | -------------------------------------------------------------------------------- /.production.env: -------------------------------------------------------------------------------- 1 | PORT=7000 2 | POSTGRES_HOST=localhost 3 | POSTGRES_USER=postgres 4 | POSTGRES_DB=nest-course 5 | POSTGRESS_PASSWORD=root 6 | POSTGRESS_PORT=5432 7 | -------------------------------------------------------------------------------- /src/posts/dto/create-post.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreatePostDto { 2 | readonly title: string; 3 | readonly content: string; 4 | readonly userId: number; 5 | } 6 | -------------------------------------------------------------------------------- /.development.env: -------------------------------------------------------------------------------- 1 | PORT=5000 2 | POSTGRES_HOST=postgres 3 | POSTGRES_USER=postgres 4 | POSTGRES_DB=nest-course 5 | POSTGRESS_PASSWORD=root 6 | POSTGRESS_PORT=5432 7 | PRIVATE_KEY=secret_key_safasf 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Не забудь звезду 2 | 3 | в файле .development.env конфигурация для бд. Добавить свою. 4 | 5 | #### npm run start:dev - Запуск 6 | 7 | ## 8 | 9 | #### docker-compose up 10 | -------------------------------------------------------------------------------- /src/auth/roles-auth.decorator.ts: -------------------------------------------------------------------------------- 1 | import {SetMetadata} from "@nestjs/common"; 2 | 3 | export const ROLES_KEY = 'roles'; 4 | 5 | export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | COPY ./dist ./dist 12 | 13 | CMD ["npm", "run", "start:dev"] 14 | -------------------------------------------------------------------------------- /src/files/files.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { FilesService } from './files.service'; 3 | 4 | @Module({ 5 | providers: [FilesService], 6 | exports: [FilesService] 7 | }) 8 | export class FilesModule {} 9 | -------------------------------------------------------------------------------- /src/users/dto/add-role.dto.ts: -------------------------------------------------------------------------------- 1 | import {IsNumber, IsString} from "class-validator"; 2 | 3 | export class AddRoleDto { 4 | @IsString({message: "Должно быть строкой"}) 5 | readonly value: string; 6 | @IsNumber({}, {message: "Должно быть числом"}) 7 | readonly userId: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/exceptions/validation.exception.ts: -------------------------------------------------------------------------------- 1 | import {HttpException, HttpStatus} from "@nestjs/common"; 2 | 3 | export class ValidationException extends HttpException { 4 | messages; 5 | 6 | constructor(response) { 7 | super(response, HttpStatus.BAD_REQUEST); 8 | this.messages = response 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /src/users/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import {ApiProperty} from "@nestjs/swagger"; 2 | import {IsEmail, IsString, Length} from "class-validator"; 3 | 4 | export class CreateUserDto { 5 | 6 | @ApiProperty({example: 'user@mail.ru', description: 'Почта'}) 7 | @IsString({message: 'Должно быть строкой'}) 8 | @IsEmail({}, {message: "Некорректный email"}) 9 | readonly email: string; 10 | @ApiProperty({example: '12345', description: 'пароль'}) 11 | @IsString({message: 'Должно быть строкой'}) 12 | @Length(4, 16, {message: 'Не меньше 4 и не больше 16'}) 13 | readonly password: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/roles/roles.controller.ts: -------------------------------------------------------------------------------- 1 | import {Body, Controller, Get, Param, Post} from '@nestjs/common'; 2 | import {RolesService} from "./roles.service"; 3 | import {CreateRoleDto} from "./dto/create-role.dto"; 4 | 5 | @Controller('roles') 6 | export class RolesController { 7 | constructor(private roleService: RolesService) {} 8 | 9 | @Post() 10 | create(@Body() dto: CreateRoleDto) { 11 | return this.roleService.createRole(dto); 12 | } 13 | 14 | @Get('/:value') 15 | getByValue(@Param('value') value: string) { 16 | return this.roleService.getRoleByValue(value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/roles/roles.module.ts: -------------------------------------------------------------------------------- 1 | import {Module} from '@nestjs/common'; 2 | import {RolesService} from './roles.service'; 3 | import {RolesController} from './roles.controller'; 4 | import {SequelizeModule} from "@nestjs/sequelize"; 5 | import {Role} from "./roles.model"; 6 | import {User} from "../users/users.model"; 7 | import {UserRoles} from "./user-roles.model"; 8 | 9 | @Module({ 10 | providers: [RolesService], 11 | controllers: [RolesController], 12 | imports: [ 13 | SequelizeModule.forFeature([Role, User, UserRoles]) 14 | ], 15 | exports: [ 16 | RolesService 17 | ] 18 | }) 19 | export class RolesModule {} 20 | -------------------------------------------------------------------------------- /src/posts/posts.controller.ts: -------------------------------------------------------------------------------- 1 | import {Body, Controller, Post, UploadedFile, UseInterceptors} from '@nestjs/common'; 2 | import {CreatePostDto} from "./dto/create-post.dto"; 3 | import {PostsService} from "./posts.service"; 4 | import {FileInterceptor} from "@nestjs/platform-express"; 5 | 6 | @Controller('posts') 7 | export class PostsController { 8 | 9 | constructor(private postService: PostsService) {} 10 | 11 | @Post() 12 | @UseInterceptors(FileInterceptor('image')) 13 | createPost(@Body() dto: CreatePostDto, 14 | @UploadedFile() image) { 15 | return this.postService.create(dto, image) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/roles/roles.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {CreateRoleDto} from "./dto/create-role.dto"; 3 | import {InjectModel} from "@nestjs/sequelize"; 4 | import {Role} from "./roles.model"; 5 | 6 | @Injectable() 7 | export class RolesService { 8 | 9 | constructor(@InjectModel(Role) private roleRepository: typeof Role) {} 10 | 11 | async createRole(dto: CreateRoleDto) { 12 | const role = await this.roleRepository.create(dto); 13 | return role; 14 | } 15 | 16 | async getRoleByValue(value: string) { 17 | const role = await this.roleRepository.findOne({where: {value}}) 18 | return role; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import {Body, Controller, Post} from '@nestjs/common'; 2 | import {ApiTags} from "@nestjs/swagger"; 3 | import {CreateUserDto} from "../users/dto/create-user.dto"; 4 | import {AuthService} from "./auth.service"; 5 | 6 | @ApiTags('Авторизация') 7 | @Controller('auth') 8 | export class AuthController { 9 | 10 | constructor(private authService: AuthService) {} 11 | 12 | @Post('/login') 13 | login(@Body() userDto: CreateUserDto) { 14 | return this.authService.login(userDto) 15 | } 16 | 17 | @Post('/registration') 18 | registration(@Body() userDto: CreateUserDto) { 19 | return this.authService.registration(userDto) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/posts/posts.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PostsService } from './posts.service'; 3 | import { PostsController } from './posts.controller'; 4 | import {SequelizeModule} from "@nestjs/sequelize"; 5 | import {User} from "../users/users.model"; 6 | import {Role} from "../roles/roles.model"; 7 | import {UserRoles} from "../roles/user-roles.model"; 8 | import {Post} from "./posts.model"; 9 | import {FilesModule} from "../files/files.module"; 10 | 11 | @Module({ 12 | providers: [PostsService], 13 | controllers: [PostsController], 14 | imports: [ 15 | SequelizeModule.forFeature([User, Post]), 16 | FilesModule 17 | ] 18 | }) 19 | export class PostsModule {} 20 | -------------------------------------------------------------------------------- /src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import {forwardRef, Module} from '@nestjs/common'; 2 | import { AuthController } from './auth.controller'; 3 | import { AuthService } from './auth.service'; 4 | import {UsersModule} from "../users/users.module"; 5 | import {JwtModule} from "@nestjs/jwt"; 6 | 7 | @Module({ 8 | controllers: [AuthController], 9 | providers: [AuthService], 10 | imports: [ 11 | forwardRef(() => UsersModule), 12 | JwtModule.register({ 13 | secret: process.env.PRIVATE_KEY || 'SECRET', 14 | signOptions: { 15 | expiresIn: '24h' 16 | } 17 | }) 18 | ], 19 | exports: [ 20 | AuthService, 21 | JwtModule 22 | ] 23 | }) 24 | export class AuthModule {} 25 | -------------------------------------------------------------------------------- /src/posts/posts.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import {CreatePostDto} from "./dto/create-post.dto"; 3 | import {InjectModel} from "@nestjs/sequelize"; 4 | import {Post} from "./posts.model"; 5 | import {FilesService} from "../files/files.service"; 6 | 7 | @Injectable() 8 | export class PostsService { 9 | 10 | constructor(@InjectModel(Post) private postRepository: typeof Post, 11 | private fileService: FilesService) {} 12 | 13 | async create(dto: CreatePostDto, image: any) { 14 | const fileName = await this.fileService.createFile(image); 15 | const post = await this.postRepository.create({...dto, image: fileName}) 16 | return post; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/roles/user-roles.model.ts: -------------------------------------------------------------------------------- 1 | import {BelongsToMany, Column, DataType, ForeignKey, Model, Table} from "sequelize-typescript"; 2 | import {ApiProperty} from "@nestjs/swagger"; 3 | import {User} from "../users/users.model"; 4 | import {Role} from "./roles.model"; 5 | 6 | 7 | @Table({tableName: 'user_roles', createdAt: false, updatedAt: false}) 8 | export class UserRoles extends Model { 9 | 10 | @Column({type: DataType.INTEGER, unique: true, autoIncrement: true, primaryKey: true}) 11 | id: number; 12 | 13 | @ForeignKey(() => Role) 14 | @Column({type: DataType.INTEGER}) 15 | roleId: number; 16 | 17 | @ForeignKey(() => User) 18 | @Column({type: DataType.INTEGER}) 19 | userId: number; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.0' 2 | 3 | services: 4 | main: 5 | container_name: main 6 | build: 7 | context: . 8 | env_file: 9 | - .development.env 10 | volumes: 11 | - .:/app 12 | - /app/node_modules 13 | ports: 14 | - 5000:5000 15 | - 9229:9229 16 | command: npm run start:dev 17 | depends_on: 18 | - postgres 19 | restart: always 20 | postgres: 21 | container_name: postgres 22 | image: postgres:12 23 | env_file: 24 | - .development.env 25 | environment: 26 | PG_DATA: /var/lib/postgresql/data 27 | ports: 28 | - 5432:5432 29 | volumes: 30 | - pgdata:/var/lib/postgresql/data 31 | restart: always 32 | 33 | volumes: 34 | pgdata: 35 | 36 | -------------------------------------------------------------------------------- /src/files/files.service.ts: -------------------------------------------------------------------------------- 1 | import {HttpException, HttpStatus, Injectable} from '@nestjs/common'; 2 | import * as path from 'path' 3 | import * as fs from 'fs'; 4 | import * as uuid from 'uuid'; 5 | 6 | @Injectable() 7 | export class FilesService { 8 | 9 | async createFile(file): Promise { 10 | try { 11 | const fileName = uuid.v4() + '.jpg'; 12 | const filePath = path.resolve(__dirname, '..', 'static') 13 | if (!fs.existsSync(filePath)) { 14 | fs.mkdirSync(filePath, {recursive: true}) 15 | } 16 | fs.writeFileSync(path.join(filePath, fileName), file.buffer) 17 | return fileName; 18 | } catch (e) { 19 | throw new HttpException('Произошла ошибка при записи файла', HttpStatus.INTERNAL_SERVER_ERROR) 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import {ArgumentMetadata, Injectable, PipeTransform} from "@nestjs/common"; 2 | import {plainToClass} from "class-transformer"; 3 | import {validate} from "class-validator"; 4 | import {ValidationException} from "../exceptions/validation.exception"; 5 | 6 | 7 | @Injectable() 8 | export class ValidationPipe implements PipeTransform { 9 | async transform(value: any, metadata: ArgumentMetadata): Promise { 10 | const obj = plainToClass(metadata.metatype, value); 11 | const errors = await validate(obj); 12 | 13 | if (errors.length) { 14 | let messages = errors.map(err => { 15 | return `${err.property} - ${Object.values(err.constraints).join(', ')}` 16 | }) 17 | throw new ValidationException(messages) 18 | } 19 | return value; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import {forwardRef, Module} from '@nestjs/common'; 2 | import { UsersController } from './users.controller'; 3 | import { UsersService } from './users.service'; 4 | import {SequelizeModule} from "@nestjs/sequelize"; 5 | import {User} from "./users.model"; 6 | import {Role} from "../roles/roles.model"; 7 | import {UserRoles} from "../roles/user-roles.model"; 8 | import {RolesModule} from "../roles/roles.module"; 9 | import {AuthModule} from "../auth/auth.module"; 10 | import {Post} from "../posts/posts.model"; 11 | 12 | @Module({ 13 | controllers: [UsersController], 14 | providers: [UsersService], 15 | imports: [ 16 | SequelizeModule.forFeature([User, Role, UserRoles, Post]), 17 | RolesModule, 18 | forwardRef(() => AuthModule), 19 | ], 20 | exports: [ 21 | UsersService, 22 | ] 23 | }) 24 | export class UsersModule {} 25 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {NestFactory} from "@nestjs/core"; 2 | import {AppModule} from "./app.module"; 3 | import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger"; 4 | import {JwtAuthGuard} from "./auth/jwt-auth.guard"; 5 | import {ValidationPipe} from "./pipes/validation.pipe"; 6 | 7 | 8 | async function start() { 9 | const PORT = process.env.PORT || 5000; 10 | const app = await NestFactory.create(AppModule) 11 | 12 | const config = new DocumentBuilder() 13 | .setTitle('Урок по продвинотому BACKEND') 14 | .setDescription('Документация REST API') 15 | .setVersion('1.0.0') 16 | .addTag('ULBI TV') 17 | .build() 18 | const document = SwaggerModule.createDocument(app, config); 19 | SwaggerModule.setup('/api/docs', app, document) 20 | 21 | app.useGlobalPipes(new ValidationPipe()) 22 | 23 | await app.listen(PORT, () => console.log(`Server started on port = ${PORT}`)) 24 | } 25 | 26 | start() 27 | -------------------------------------------------------------------------------- /src/roles/roles.model.ts: -------------------------------------------------------------------------------- 1 | import {BelongsToMany, Column, DataType, Model, Table} from "sequelize-typescript"; 2 | import {ApiProperty} from "@nestjs/swagger"; 3 | import {User} from "../users/users.model"; 4 | import {UserRoles} from "./user-roles.model"; 5 | 6 | interface RoleCreationAttrs { 7 | value: string; 8 | description: string; 9 | } 10 | 11 | @Table({tableName: 'roles'}) 12 | export class Role extends Model { 13 | 14 | @ApiProperty({example: '1', description: 'Уникальный идентификатор'}) 15 | @Column({type: DataType.INTEGER, unique: true, autoIncrement: true, primaryKey: true}) 16 | id: number; 17 | 18 | @ApiProperty({example: 'ADMIN', description: 'Уникальное Значение роли '}) 19 | @Column({type: DataType.STRING, unique: true, allowNull: false}) 20 | value: string; 21 | 22 | @ApiProperty({example: 'Администратор', description: 'Описание роли'}) 23 | @Column({type: DataType.STRING, allowNull: false}) 24 | description: string; 25 | 26 | @BelongsToMany(() => User, () => UserRoles) 27 | users: User[]; 28 | } 29 | -------------------------------------------------------------------------------- /src/posts/posts.model.ts: -------------------------------------------------------------------------------- 1 | import {BelongsTo, BelongsToMany, Column, DataType, ForeignKey, Model, Table} from "sequelize-typescript"; 2 | import {ApiProperty} from "@nestjs/swagger"; 3 | import {Role} from "../roles/roles.model"; 4 | import {UserRoles} from "../roles/user-roles.model"; 5 | import {User} from "../users/users.model"; 6 | 7 | interface PostCreationAttrs { 8 | title: string; 9 | content: string; 10 | userId: number; 11 | image: string; 12 | } 13 | 14 | @Table({tableName: 'posts'}) 15 | export class Post extends Model { 16 | @Column({type: DataType.INTEGER, unique: true, autoIncrement: true, primaryKey: true}) 17 | id: number; 18 | 19 | @Column({type: DataType.STRING, unique: true, allowNull: false}) 20 | title: string; 21 | 22 | @Column({type: DataType.STRING, allowNull: false}) 23 | content: string; 24 | 25 | @Column({type: DataType.STRING}) 26 | image: string; 27 | 28 | @ForeignKey(() => User) 29 | @Column({type: DataType.INTEGER}) 30 | userId: number; 31 | 32 | @BelongsTo(() => User) 33 | author: User 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/auth/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import {CanActivate, ExecutionContext, Injectable, UnauthorizedException} from "@nestjs/common"; 2 | import {Observable} from "rxjs"; 3 | import {JwtService} from "@nestjs/jwt"; 4 | 5 | @Injectable() 6 | export class JwtAuthGuard implements CanActivate { 7 | constructor(private jwtService: JwtService) { 8 | } 9 | 10 | canActivate(context: ExecutionContext): boolean | Promise | Observable { 11 | const req = context.switchToHttp().getRequest() 12 | try { 13 | const authHeader = req.headers.authorization; 14 | const bearer = authHeader.split(' ')[0] 15 | const token = authHeader.split(' ')[1] 16 | 17 | if (bearer !== 'Bearer' || !token) { 18 | throw new UnauthorizedException({message: 'Пользователь не авторизован'}) 19 | } 20 | 21 | const user = this.jwtService.verify(token); 22 | req.user = user; 23 | return true; 24 | } catch (e) { 25 | throw new UnauthorizedException({message: 'Пользователь не авторизован'}) 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/users/users.model.ts: -------------------------------------------------------------------------------- 1 | import {BelongsToMany, Column, DataType, HasMany, Model, Table} from "sequelize-typescript"; 2 | import {ApiProperty} from "@nestjs/swagger"; 3 | import {Role} from "../roles/roles.model"; 4 | import {UserRoles} from "../roles/user-roles.model"; 5 | import {Post} from "../posts/posts.model"; 6 | 7 | interface UserCreationAttrs { 8 | email: string; 9 | password: string; 10 | } 11 | 12 | @Table({tableName: 'users'}) 13 | export class User extends Model { 14 | @ApiProperty({example: '1', description: 'Уникальный идентификатор'}) 15 | @Column({type: DataType.INTEGER, unique: true, autoIncrement: true, primaryKey: true}) 16 | id: number; 17 | @ApiProperty({example: 'user@mail.ru', description: 'Почтовый адрес'}) 18 | @Column({type: DataType.STRING, unique: true, allowNull: false}) 19 | email: string; 20 | @ApiProperty({example: '12345678', description: 'Пароль'}) 21 | @Column({type: DataType.STRING, allowNull: false}) 22 | password: string; 23 | 24 | @ApiProperty({example: 'true', description: 'Забанен или нет'}) 25 | @Column({type: DataType.BOOLEAN, defaultValue: false}) 26 | banned: boolean; 27 | 28 | @ApiProperty({example: 'За хулиганство', description: 'Причина блокировки'}) 29 | @Column({type: DataType.STRING, allowNull: true}) 30 | banReason: string; 31 | 32 | @BelongsToMany(() => Role, () => UserRoles) 33 | roles: Role[]; 34 | 35 | @HasMany(() => Post) 36 | posts: Post[]; 37 | } 38 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import {Module} from "@nestjs/common"; 2 | import {SequelizeModule} from "@nestjs/sequelize"; 3 | import { UsersModule } from './users/users.module'; 4 | import {ConfigModule} from "@nestjs/config"; 5 | import {User} from "./users/users.model"; 6 | import { RolesModule } from './roles/roles.module'; 7 | import {Role} from "./roles/roles.model"; 8 | import {UserRoles} from "./roles/user-roles.model"; 9 | import { AuthModule } from './auth/auth.module'; 10 | import { PostsModule } from './posts/posts.module'; 11 | import {Post} from "./posts/posts.model"; 12 | import { FilesModule } from './files/files.module'; 13 | import {ServeStaticModule} from "@nestjs/serve-static"; 14 | import * as path from 'path'; 15 | 16 | @Module({ 17 | controllers: [], 18 | providers: [], 19 | imports: [ 20 | ConfigModule.forRoot({ 21 | envFilePath: `.${process.env.NODE_ENV}.env` 22 | }), 23 | ServeStaticModule.forRoot({ 24 | rootPath: path.resolve( __dirname, 'static'), 25 | }), 26 | SequelizeModule.forRoot({ 27 | dialect: 'postgres', 28 | host: process.env.POSTGRES_HOST, 29 | port: Number(process.env.POSTGRESS_PORT), 30 | username: process.env.POSTGRES_USER, 31 | password: process.env.POSTGRESS_PASSWORD, 32 | database: process.env.POSTGRES_DB, 33 | models: [User, Role, UserRoles, Post], 34 | autoLoadModels: true 35 | }), 36 | UsersModule, 37 | RolesModule, 38 | AuthModule, 39 | PostsModule, 40 | FilesModule, 41 | ] 42 | }) 43 | export class AppModule {} 44 | -------------------------------------------------------------------------------- /src/auth/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | HttpException, 5 | HttpStatus, 6 | Injectable, 7 | UnauthorizedException 8 | } from "@nestjs/common"; 9 | import {Observable} from "rxjs"; 10 | import {JwtService} from "@nestjs/jwt"; 11 | import {Reflector} from "@nestjs/core"; 12 | import {ROLES_KEY} from "./roles-auth.decorator"; 13 | 14 | @Injectable() 15 | export class RolesGuard implements CanActivate { 16 | constructor(private jwtService: JwtService, 17 | private reflector: Reflector) { 18 | } 19 | 20 | canActivate(context: ExecutionContext): boolean | Promise | Observable { 21 | try { 22 | const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ 23 | context.getHandler(), 24 | context.getClass(), 25 | ]) 26 | if (!requiredRoles) { 27 | return true; 28 | } 29 | const req = context.switchToHttp().getRequest(); 30 | const authHeader = req.headers.authorization; 31 | const bearer = authHeader.split(' ')[0] 32 | const token = authHeader.split(' ')[1] 33 | 34 | if (bearer !== 'Bearer' || !token) { 35 | throw new UnauthorizedException({message: 'Пользователь не авторизован'}) 36 | } 37 | 38 | const user = this.jwtService.verify(token); 39 | req.user = user; 40 | return user.roles.some(role => requiredRoles.includes(role.value)); 41 | } catch (e) { 42 | console.log(e) 43 | throw new HttpException( 'Нет доступа', HttpStatus.FORBIDDEN) 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import {HttpException, HttpStatus, Injectable, UnauthorizedException} from '@nestjs/common'; 2 | import {CreateUserDto} from "../users/dto/create-user.dto"; 3 | import {UsersService} from "../users/users.service"; 4 | import {JwtService} from "@nestjs/jwt"; 5 | import * as bcrypt from 'bcryptjs' 6 | import {User} from "../users/users.model"; 7 | 8 | @Injectable() 9 | export class AuthService { 10 | 11 | constructor(private userService: UsersService, 12 | private jwtService: JwtService) {} 13 | 14 | async login(userDto: CreateUserDto) { 15 | const user = await this.validateUser(userDto) 16 | return this.generateToken(user) 17 | } 18 | 19 | async registration(userDto: CreateUserDto) { 20 | const candidate = await this.userService.getUserByEmail(userDto.email); 21 | if (candidate) { 22 | throw new HttpException('Пользователь с таким email существует', HttpStatus.BAD_REQUEST); 23 | } 24 | const hashPassword = await bcrypt.hash(userDto.password, 5); 25 | const user = await this.userService.createUser({...userDto, password: hashPassword}) 26 | return this.generateToken(user) 27 | } 28 | 29 | private async generateToken(user: User) { 30 | const payload = {email: user.email, id: user.id, roles: user.roles} 31 | return { 32 | token: this.jwtService.sign(payload) 33 | } 34 | } 35 | 36 | private async validateUser(userDto: CreateUserDto) { 37 | const user = await this.userService.getUserByEmail(userDto.email); 38 | const passwordEquals = await bcrypt.compare(userDto.password, user.password); 39 | if (user && passwordEquals) { 40 | return user; 41 | } 42 | throw new UnauthorizedException({message: 'Некорректный емайл или пароль'}) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import {Body, Controller, Get, Post, UseGuards, UsePipes} from '@nestjs/common'; 2 | import {CreateUserDto} from "./dto/create-user.dto"; 3 | import {UsersService} from "./users.service"; 4 | import {ApiOperation, ApiResponse, ApiTags} from "@nestjs/swagger"; 5 | import {User} from "./users.model"; 6 | import {JwtAuthGuard} from "../auth/jwt-auth.guard"; 7 | import {Roles} from "../auth/roles-auth.decorator"; 8 | import {RolesGuard} from "../auth/roles.guard"; 9 | import {AddRoleDto} from "./dto/add-role.dto"; 10 | import {BanUserDto} from "./dto/ban-user.dto"; 11 | import {ValidationPipe} from "../pipes/validation.pipe"; 12 | 13 | @ApiTags('Пользователи') 14 | @Controller('users') 15 | export class UsersController { 16 | 17 | constructor(private usersService: UsersService) {} 18 | 19 | @ApiOperation({summary: 'Создание пользователя'}) 20 | @ApiResponse({status: 200, type: User}) 21 | @Post() 22 | create(@Body() userDto: CreateUserDto) { 23 | return this.usersService.createUser(userDto); 24 | } 25 | 26 | @ApiOperation({summary: 'Получить всех пользователей'}) 27 | @ApiResponse({status: 200, type: [User]}) 28 | @Roles("ADMIN") 29 | @UseGuards(RolesGuard) 30 | @Get() 31 | getAll() { 32 | return this.usersService.getAllUsers(); 33 | } 34 | 35 | @ApiOperation({summary: 'Выдать роль'}) 36 | @ApiResponse({status: 200}) 37 | @Roles("ADMIN") 38 | @UseGuards(RolesGuard) 39 | @Post('/role') 40 | addRole(@Body() dto: AddRoleDto) { 41 | return this.usersService.addRole(dto); 42 | } 43 | 44 | @ApiOperation({summary: 'Забанить пользователя'}) 45 | @ApiResponse({status: 200}) 46 | @Roles("ADMIN") 47 | @UseGuards(RolesGuard) 48 | @Post('/ban') 49 | ban(@Body() dto: BanUserDto) { 50 | return this.usersService.ban(dto); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import {HttpException, HttpStatus, Injectable} from '@nestjs/common'; 2 | import {User} from "./users.model"; 3 | import {InjectModel} from "@nestjs/sequelize"; 4 | import {CreateUserDto} from "./dto/create-user.dto"; 5 | import {RolesService} from "../roles/roles.service"; 6 | import {AddRoleDto} from "./dto/add-role.dto"; 7 | import {BanUserDto} from "./dto/ban-user.dto"; 8 | 9 | @Injectable() 10 | export class UsersService { 11 | 12 | constructor(@InjectModel(User) private userRepository: typeof User, 13 | private roleService: RolesService) {} 14 | 15 | async createUser(dto: CreateUserDto) { 16 | const user = await this.userRepository.create(dto); 17 | const role = await this.roleService.getRoleByValue("ADMIN") 18 | await user.$set('roles', [role.id]) 19 | user.roles = [role] 20 | return user; 21 | } 22 | 23 | async getAllUsers() { 24 | const users = await this.userRepository.findAll({include: {all: true}}); 25 | return users; 26 | } 27 | 28 | async getUserByEmail(email: string) { 29 | const user = await this.userRepository.findOne({where: {email}, include: {all: true}}) 30 | return user; 31 | } 32 | 33 | async addRole(dto: AddRoleDto) { 34 | const user = await this.userRepository.findByPk(dto.userId); 35 | const role = await this.roleService.getRoleByValue(dto.value); 36 | if (role && user) { 37 | await user.$add('role', role.id); 38 | return dto; 39 | } 40 | throw new HttpException('Пользователь или роль не найдены', HttpStatus.NOT_FOUND); 41 | } 42 | 43 | async ban(dto: BanUserDto) { 44 | const user = await this.userRepository.findByPk(dto.userId); 45 | if (!user) { 46 | throw new HttpException('Пользователь не найден', HttpStatus.NOT_FOUND); 47 | } 48 | user.banned = true; 49 | user.banReason = dto.banReason; 50 | await user.save(); 51 | return user; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-course", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "cross-env NODE_ENV=production nest start", 13 | "start:dev": "cross-env NODE_ENV=development nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/common": "^7.5.1", 25 | "@nestjs/config": "^0.6.3", 26 | "@nestjs/core": "^7.5.1", 27 | "@nestjs/jwt": "^7.2.0", 28 | "@nestjs/platform-express": "^7.5.1", 29 | "@nestjs/sequelize": "^0.2.0", 30 | "@nestjs/serve-static": "^2.1.4", 31 | "@nestjs/swagger": "^4.8.0", 32 | "bcryptjs": "^2.4.3", 33 | "class-transformer": "^0.4.0", 34 | "class-validator": "^0.13.1", 35 | "cross-env": "^7.0.3", 36 | "pg": "^8.5.1", 37 | "pg-hstore": "^2.3.3", 38 | "reflect-metadata": "^0.1.13", 39 | "rimraf": "^3.0.2", 40 | "rxjs": "^6.6.3", 41 | "sequelize": "^6.6.2", 42 | "sequelize-typescript": "^2.1.0", 43 | "swagger-ui-express": "^4.1.6", 44 | "uuid": "^8.3.2" 45 | }, 46 | "devDependencies": { 47 | "@nestjs/cli": "^7.5.1", 48 | "@nestjs/schematics": "^7.1.3", 49 | "@nestjs/testing": "^7.5.1", 50 | "@types/express": "^4.17.8", 51 | "@types/jest": "^26.0.15", 52 | "@types/node": "^14.14.6", 53 | "@types/sequelize": "^4.28.9", 54 | "@types/supertest": "^2.0.10", 55 | "@typescript-eslint/eslint-plugin": "^4.6.1", 56 | "@typescript-eslint/parser": "^4.6.1", 57 | "eslint": "^7.12.1", 58 | "eslint-config-prettier": "7.2.0", 59 | "eslint-plugin-prettier": "^3.1.4", 60 | "jest": "^26.6.3", 61 | "prettier": "^2.1.2", 62 | "supertest": "^6.0.0", 63 | "ts-jest": "^26.4.3", 64 | "ts-loader": "^8.0.8", 65 | "ts-node": "^9.0.0", 66 | "tsconfig-paths": "^3.9.0", 67 | "typescript": "^4.0.5" 68 | }, 69 | "jest": { 70 | "moduleFileExtensions": [ 71 | "js", 72 | "json", 73 | "ts" 74 | ], 75 | "rootDir": "src", 76 | "testRegex": ".*\\.spec\\.ts$", 77 | "transform": { 78 | "^.+\\.(t|j)s$": "ts-jest" 79 | }, 80 | "collectCoverageFrom": [ 81 | "**/*.(t|j)s" 82 | ], 83 | "coverageDirectory": "../coverage", 84 | "testEnvironment": "node" 85 | } 86 | } 87 | --------------------------------------------------------------------------------