add

parent 1bc644dc
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"scripts": { "scripts": {
"build": "nest build", "build": "nest build",
"seed": "ts-node -r tsconfig-paths/register src/db/seed.ts",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start", "start": "nest start",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",
...@@ -20,22 +21,30 @@ ...@@ -20,22 +21,30 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "^10.0.0",
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2", "@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.0",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/serve-static": "^5.0.3", "@nestjs/serve-static": "^5.0.3",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"cookie-parser": "^1.4.7",
"dotenv": "^17.2.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"multer": "^2.0.2", "multer": "^2.0.2",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pg": "^8.16.3", "pg": "^8.16.3",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"typeorm": "^0.3.26" "typeorm": "^0.3.26",
"typeorm-extension": "^3.7.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
...@@ -46,11 +55,13 @@ ...@@ -46,11 +55,13 @@
"@swc/cli": "^0.6.0", "@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7", "@swc/core": "^1.10.7",
"@types/bcrypt": "^6.0.0", "@types/bcrypt": "^6.0.0",
"@types/cookie-parser": "^1.4.9",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/lodash": "^4.17.20", "@types/lodash": "^4.17.20",
"@types/multer": "^2.0.0", "@types/multer": "^2.0.0",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
......
This diff is collapsed.
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { omit } from 'lodash';
import { User } from 'src/user/entities/user.entity'; import { User } from 'src/user/entities/user.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { LoginAuthDto } from './dto/login.dto'; import { LoginAuthDto } from './dto/login.dto';
import { RegisterAuthDto } from './dto/register.dto'; import { RegisterAuthDto } from './dto/register.dto';
import { omit } from 'lodash';
const SALT_WORK_FACTOR = 10; const SALT_WORK_FACTOR = 10;
......
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
import { appDataSource } from 'src/db/dataSource';
import { User } from 'src/user/entities/user.entity';
@Injectable()
export class AuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const userRepo = appDataSource.getRepository(User);
const request: Request = context.switchToHttp().getRequest();
const authHeader = request.headers['authorization'];
if (!authHeader) {
throw new UnauthorizedException('Токена нет');
}
try {
const token = authHeader;
const user = await userRepo.findOneBy({ token });
if (!user) {
throw new UnauthorizedException('Юзера нет');
}
return true;
} catch {
throw new UnauthorizedException('Ошибка авторизации');
}
}
}
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { appDataSource } from 'src/db/dataSource';
import { ROLES_KEY } from 'src/decorators/roles.decorator';
import { User } from 'src/user/entities/user.entity';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const userRepo = appDataSource.getRepository(User);
const request: Request = context.switchToHttp().getRequest();
const authHeader = request.headers['authorization'];
if (!authHeader) {
throw new UnauthorizedException('Токена нет');
}
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true; // если роли не указаны — доступ открыт
}
const token = authHeader;
const user = await userRepo.findOneBy({ token });
if (!user) {
throw new UnauthorizedException('Юзера нет');
}
if (!user || !requiredRoles.includes(user?.role || '')) {
throw new ForbiddenException('Permission denied');
}
return true;
}
}
import * as dotenv from 'dotenv';
import * as path from 'path';
import { Category } from 'src/categories/entities/category.entity';
import { Product } from 'src/products/entity/product';
import { User } from 'src/user/entities/user.entity';
import { DataSource, DataSourceOptions } from 'typeorm';
import { SeederOptions } from 'typeorm-extension';
import { categoryFactory } from './factories/category.factory';
import { productFactory } from './factories/product.factory';
import { userFactory } from './factories/user.factory';
import MainSeeder from './seeders/main.seeder';
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
const options: DataSourceOptions & SeederOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
synchronize: true,
logging: true,
entities: [User, Category, Product],
seeds: [MainSeeder],
factories: [userFactory, categoryFactory, productFactory],
};
export const appDataSource = new DataSource(options);
import { faker } from '@faker-js/faker';
import { Category } from 'src/categories/entities/category.entity';
import { setSeederFactory } from 'typeorm-extension';
export const categoryFactory = setSeederFactory(Category, () => {
const category = new Category();
category.title = faker.commerce.department();
category.description = faker.lorem.sentence();
return category;
});
import { faker } from '@faker-js/faker';
import { Product } from 'src/products/entity/product';
import { setSeederFactory } from 'typeorm-extension';
export const productFactory = setSeederFactory(Product, () => {
const product = new Product();
product.title = faker.commerce.productName();
product.price = faker.number.int({ min: 100, max: 2000 });
product.description = faker.lorem.sentence();
product.image = 'testProduct.jpg';
return product;
});
import { faker } from '@faker-js/faker';
import * as bcrypt from 'bcrypt';
import { User } from 'src/user/entities/user.entity';
import { setSeederFactory } from 'typeorm-extension';
const SALT_WORK_FACTOR = 10;
export const userFactory = setSeederFactory(User, async () => {
const user = new User();
user.displayName = faker.person.firstName();
user.userName = faker.internet.username();
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
user.password = await bcrypt.hash('password', salt);
return user;
});
import { runSeeders } from 'typeorm-extension';
import { appDataSource } from './dataSource';
async function seed() {
try {
await appDataSource.initialize();
await runSeeders(appDataSource);
} catch (e) {
console.log('Фикстуры не запустились');
console.error(e);
}
}
seed();
import { faker } from '@faker-js/faker';
import { Category } from 'src/categories/entities/category.entity';
import { Product } from 'src/products/entity/product';
import { User } from 'src/user/entities/user.entity';
import { DataSource } from 'typeorm';
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
export default class MainSeeder implements Seeder {
public async run(
_dataSource: DataSource,
factoryManager: SeederFactoryManager,
): Promise<void> {
const userFactory = factoryManager.get(User);
const productFactory = factoryManager.get(Product);
const categoryFactory = factoryManager.get(Category);
await userFactory.saveMany(20);
const categories = await categoryFactory.saveMany(10);
await productFactory.saveMany(10, {
category: faker.helpers.arrayElement(categories),
});
}
}
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
...@@ -3,6 +3,7 @@ import { NestFactory } from '@nestjs/core'; ...@@ -3,6 +3,7 @@ import { NestFactory } from '@nestjs/core';
import * as express from 'express'; import * as express from 'express';
import { join } from 'path'; import { join } from 'path';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { appDataSource } from './db/dataSource';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
...@@ -24,6 +25,7 @@ async function bootstrap() { ...@@ -24,6 +25,7 @@ async function bootstrap() {
app.use('/uploads', express.static(join(__dirname, '..', 'uploads'))); app.use('/uploads', express.static(join(__dirname, '..', 'uploads')));
await app.listen(process.env.PORT ?? 8000); await app.listen(process.env.PORT ?? 8000);
await appDataSource.initialize();
console.log('Server started on http://localhost:8000'); console.log('Server started on http://localhost:8000');
} }
bootstrap(); bootstrap();
...@@ -4,10 +4,14 @@ import { ...@@ -4,10 +4,14 @@ import {
Get, Get,
Post, Post,
UploadedFile, UploadedFile,
UseGuards,
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express'; import { FileInterceptor } from '@nestjs/platform-express';
import { AuthGuard } from 'src/common/guards/auth.guard';
import { RolesGuard } from 'src/common/guards/roles.guard';
import { storage } from 'src/config'; import { storage } from 'src/config';
import { Roles } from 'src/decorators/roles.decorator';
import { CreateProductDto } from './dto/create-product.dto'; import { CreateProductDto } from './dto/create-product.dto';
import { ProductsService } from './products.service'; import { ProductsService } from './products.service';
...@@ -16,6 +20,8 @@ export class ProductsController { ...@@ -16,6 +20,8 @@ export class ProductsController {
constructor(private readonly productsService: ProductsService) {} constructor(private readonly productsService: ProductsService) {}
@Post() @Post()
@UseGuards(AuthGuard, RolesGuard)
@Roles('admin')
@UseInterceptors(FileInterceptor('image', storage)) @UseInterceptors(FileInterceptor('image', storage))
create( create(
@Body() createProductDto: CreateProductDto, @Body() createProductDto: CreateProductDto,
...@@ -25,6 +31,8 @@ export class ProductsController { ...@@ -25,6 +31,8 @@ export class ProductsController {
} }
@Get() @Get()
@UseGuards(AuthGuard, RolesGuard)
@Roles('user', 'admin')
findAll() { findAll() {
return this.productsService.findAll(); return this.productsService.findAll();
} }
......
...@@ -8,6 +8,10 @@ export class CreateUserDto { ...@@ -8,6 +8,10 @@ export class CreateUserDto {
@IsString() @IsString()
displayName: string; displayName: string;
@IsOptional()
@IsString()
role?: 'user' | 'admin';
@IsString() @IsString()
password: string; password: string;
} }
...@@ -12,6 +12,9 @@ export class User { ...@@ -12,6 +12,9 @@ export class User {
@Column({ nullable: true }) @Column({ nullable: true })
displayName?: string; displayName?: string;
@Column({ default: 'user' })
role?: 'user' | 'admin';
@Column() @Column()
password: string; password: string;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment