· 6 months ago · Apr 09, 2025, 09:20 AM
1import { Injectable, Controller, Post, Body, Get, Param, UseGuards, BadRequestException, NotFoundException, UnauthorizedException, Module } from '@nestjs/common';
2import { InjectRepository } from '@nestjs/typeorm';
3import { Repository, Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
4import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
5import { JwtService } from '@nestjs/jwt';
6import { AuthGuard } from '@nestjs/passport';
7import { validate } from 'class-validator';
8import * as bcrypt from 'bcrypt';
9import { IsString, IsEmail, MinLength, IsNumber, IsPositive } from 'class-validator';
10import { InjectQueue } from '@nestjs/bull';
11import { Queue } from 'bull';
12
13@Entity()
14export class User {
15 @PrimaryGeneratedColumn()
16 id: number;
17
18 @Column()
19 @IsString()
20 name: string;
21
22 @Column({ unique: true })
23 @IsEmail()
24 email: string;
25
26 @Column()
27 @IsString()
28 @MinLength(8)
29 password: string;
30}
31
32@Entity()
33export class Order {
34 @PrimaryGeneratedColumn()
35 id: number;
36
37 @ManyToOne(() => User, (user) => user.id)
38 user: User;
39
40 @Column()
41 @IsNumber()
42 @IsPositive()
43 totalPrice: number;
44
45 @Column({ default: 'pending' })
46 status: string;
47}
48
49
50export class CreateOrderDto {
51 @IsNumber()
52 @IsPositive()
53 totalPrice: number;
54}
55
56@Injectable()
57export class OrderService {
58 private readonly saltRounds = 10;
59
60 constructor(
61 @InjectRepository(User)
62 private readonly userRepository: Repository<User>,
63 @InjectRepository(Order)
64 private readonly orderRepository: Repository<Order>,
65 private readonly jwtService: JwtService,
66 @InjectQueue('notifications')
67 private readonly notificationQueue: Queue,
68 ) {}
69
70 async register(name: string, email: string, password: string): Promise<{ accessToken: string }> {
71 const existingUser = await this.userRepository.findOne({ where: { email } });
72 if (existingUser) {
73 throw new BadRequestException('Email уже занят');
74 }
75
76 const hashedPassword = await bcrypt.hash(password, this.saltRounds);
77 const user = this.userRepository.create({ name, email, password: hashedPassword });
78 await this.userRepository.save(user);
79
80 const payload = { sub: user.id, email: user.email };
81 return { accessToken: this.jwtService.sign(payload) };
82 }
83
84 async createOrder(userId: number, createOrderDto: CreateOrderDto): Promise<Order> {
85 const user = await this.userRepository.findOne({ where: { id: userId } });
86 if (!user) {
87 throw new NotFoundException('Пользователь не найден');
88 }
89
90 const errors = await validate(createOrderDto);
91 if (errors.length > 0) {
92 throw new BadRequestException('Некорректные данные заказа');
93 }
94
95 let { totalPrice } = createOrderDto;
96
97 if (totalPrice > 1000) {
98 totalPrice *= 0.9; // скидка
99 }
100
101 const order = this.orderRepository.create({ user, totalPrice, status: 'pending' });
102 await this.orderRepository.save(order);
103
104 await this.notificationQueue.add('sendOrderConfirmation', {
105 userId: user.id,
106 orderId: order.id,
107 email: user.email,
108 });
109
110 return order;
111 }
112
113 async getOrderStatus(orderId: number): Promise<string> {
114 const order = await this.orderRepository.findOne({ where: { id: orderId } });
115 if (!order) {
116 throw new NotFoundException('Заказ не найден');
117 }
118 return order.status;
119 }
120
121 async updateOrderStatus(orderId: number, status: string): Promise<void> {
122 const order = await this.orderRepository.findOne({ where: { id: orderId } });
123 if (!order) {
124 throw new NotFoundException('Заказ не найден');
125 }
126
127 order.status = status;
128 await this.orderRepository.save(order);
129
130 if (status === 'completed') {
131 await this.notificationQueue.add('sendOrderCompleted', {
132 orderId: order.id,
133 email: order.user.email,
134 });
135 }
136 }
137}
138
139@ApiTags('orders')
140@Controller('orders')
141export class OrderController {
142 constructor(private readonly orderService: OrderService) {}
143
144 @Post('register')
145 async register(@Body() body: { name: string; email: string; password: string }) {
146 return this.orderService.register(body.name, body.email, body.password);
147 }
148
149 @Post()
150 @UseGuards(AuthGuard('jwt'))
151 @ApiBearerAuth()
152 async createOrder(@Body() createOrderDto: CreateOrderDto, @Param('userId') userId: number) {
153 return this.orderService.createOrder(userId, createOrderDto);
154 }
155
156 @Get(':orderId/status')
157 @UseGuards(AuthGuard('jwt'))
158 @ApiBearerAuth()
159 async getOrderStatus(@Param('orderId') orderId: string) {
160 return this.orderService.getOrderStatus(+orderId);
161 }
162
163 @Post(':orderId/status')
164 @UseGuards(AuthGuard('jwt'))
165 @ApiBearerAuth()
166 async updateOrderStatus(@Param('orderId') orderId: string, @Body('status') status: string) {
167 await this.orderService.updateOrderStatus(+orderId, status);
168 return { message: 'Статус обновлен' };
169 }
170}
171
172@Module({
173 providers: [OrderService],
174 controllers: [OrderController],
175})
176export class AppModule {}