added user management #35

Merged
VladislavD merged 1 commits from swagger into rc 2025-08-08 16:03:30 +03:00
6 changed files with 13025 additions and 11215 deletions

34
.env
View File

@ -1,19 +1,19 @@
# Прометеус # Прометеус
#PROMETHEUS_API=http://192.168.2.34:9090/api/v1 PROMETHEUS_API=http://192.168.2.34:9090/api/v1
#FRONTEND_URL=192.168.2.39:5173 FRONTEND_URL=localhost:5173
# Постгресс # Постгресс
#DB_HOST=192.168.2.37 DB_HOST=192.168.2.37
#DB_PORT=5432 DB_PORT=5432
#DB_USER=trust DB_USER=trust
#DB_PASSWORD=kaiqolzp2a4aH DB_PASSWORD=kaiqolzp2a4aH
#DB_NAME=trust-db DB_NAME=trust-db
# JWT # JWT
#JWT_SECRET=x7F!2p9L#q1$z0*8R5vYgMnBk #JWT_SECRET=x7F!2p9L#q1$z0*8R5vYgMnBk
#JWT_SECRET=x7Fcdp9L#q1$z0*8R5vYgMnBk JWT_SECRET=x7Fcdp9Lq1$z0*8R5vYgMnBk
# COOKIE # COOKIE
# Для production # Для production
@ -21,18 +21,18 @@
#COOKIE_SAME_SITE=strict #COOKIE_SAME_SITE=strict
# Для development # Для development
#COOKIE_SECURE=false COOKIE_SECURE=false
#COOKIE_SAME_SITE=lax COOKIE_SAME_SITE=lax
# Для меню # Для меню
#RANGES_API_URL=http://192.168.2.39:9999 RANGES_API_URL=http://192.168.2.39:9999
#RANGES_API_ENDPOINT=/api/ranges/9999 RANGES_API_ENDPOINT=/api/ranges/9999
# ClickHouse # ClickHouse
#CLICKHOUSE_HOST=http://192.168.2.37:8123 CLICKHOUSE_HOST=http://192.168.2.37:8123
#CLICKHOUSE_USER=vlad CLICKHOUSE_USER=vlad
#CLICKHOUSE_PASSWORD=vlad CLICKHOUSE_PASSWORD=vlad
#CLICKHOUSE_DB=zvks CLICKHOUSE_DB=zvks
# Для ai api # Для ai api
#AI_SERVICE_URL=http://192.168.2.39:5134 AI_SERVICE_URL=http://192.168.2.39:5134

View File

@ -10,4 +10,6 @@ COPY . .
ENV NODE_ENV=development ENV NODE_ENV=development
EXPOSE 3000
CMD ["npm", "run", "start:dev"] CMD ["npm", "run", "start:dev"]

24073
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,9 @@
import { Controller, Post, Get, Body, Res, Req, UnauthorizedException, UseGuards } from '@nestjs/common'; import { Controller, Post, Get, Body, Res, Req, UnauthorizedException, UseGuards, ForbiddenException, Delete, Param } from '@nestjs/common';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { Response, Request } from 'express'; import { Response, Request } from 'express';
import { JwtAuthGuard } from './jwt-auth.guard'; import { JwtAuthGuard } from './jwt-auth.guard';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { User } from './user.entity';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
@ -77,4 +78,41 @@ export class AuthController {
res.clearCookie('access_token'); res.clearCookie('access_token');
return { success: true }; return { success: true };
} }
@UseGuards(JwtAuthGuard)
@Get('users')
async getAllUsers(@Req() req: Request) {
const user = req.user as User;
const isAdmin = await this.authService.isAdmin(user.id);
if (!isAdmin) {
throw new ForbiddenException('Only admin can access this resource');
}
return this.authService.getAllUsers();
}
@UseGuards(JwtAuthGuard)
@Post('users')
async createUser(
@Body() body: { login: string; password: string; role?: 'user' | 'admin' },
@Req() req: Request
) {
const user = req.user as User;
const isAdmin = await this.authService.isAdmin(user.id);
if (!isAdmin) {
throw new ForbiddenException('Only admin can create users');
}
return this.authService.createUser(body.login, body.password, body.role);
}
@UseGuards(JwtAuthGuard)
@Delete('users/:id')
async deleteUser(@Param('id') id: string, @Req() req: Request) {
const user = req.user as User;
const isAdmin = await this.authService.isAdmin(user.id);
if (!isAdmin) {
throw new ForbiddenException('Only admin can delete users');
}
await this.authService.deleteUser(parseInt(id));
return { message: 'User deleted successfully' };
}
} }

View File

@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable, ForbiddenException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { User } from './user.entity'; import { User } from './user.entity';
import * as bcrypt from 'bcrypt';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@ -12,6 +13,7 @@ export class AuthService {
private jwtService: JwtService, private jwtService: JwtService,
) { } ) { }
async validateUser(login: string, password: string): Promise<any> { async validateUser(login: string, password: string): Promise<any> {
const user = await this.usersRepository.findOne({ where: { login } }); const user = await this.usersRepository.findOne({ where: { login } });
@ -25,6 +27,8 @@ export class AuthService {
return null; return null;
} }
async login(user: any) { async login(user: any) {
const payload = { const payload = {
username: user.login, username: user.login,
@ -35,4 +39,33 @@ export class AuthService {
access_token: this.jwtService.sign(payload), access_token: this.jwtService.sign(payload),
}; };
} }
async getAllUsers(): Promise<User[]> {
return this.usersRepository.find();
}
async createUser(login: string, password: string, role: 'user' | 'admin' = 'user'): Promise<User> {
// const hashedPassword = await bcrypt.hash(password, 10);
const user = this.usersRepository.create({
login,
password, //hashedPassword,
role
});
return this.usersRepository.save(user);
}
async deleteUser(id: number): Promise<void> {
const user = await this.usersRepository.findOne({ where: { id } });
if (user && user.role === 'admin') {
throw new ForbiddenException('Cannot delete admin user');
}
await this.usersRepository.delete(id);
}
async isAdmin(userId: number): Promise<boolean> {
const user = await this.usersRepository.findOne({ where: { id: userId } });
return user?.role === 'admin';
}
} }

View File

@ -6,33 +6,33 @@ import { MenuItem } from './menu.interface';
export class MenuController { export class MenuController {
constructor(private readonly menuService: MenuService) { } constructor(private readonly menuService: MenuService) { }
@Get('full')
async getFullMenu(@Headers('if-modified-since') ifModifiedSince?: string) {
console.log('GET /menu/full requested');
try {
const result = await this.menuService.getFullMenuWithCache(ifModifiedSince);
if (!result.fresh && ifModifiedSince) {
throw new HttpException('Not Modified', HttpStatus.NOT_MODIFIED);
}
return result.menu;
} catch (error) {
if (error.status === HttpStatus.NOT_MODIFIED) {
throw error;
}
throw new HttpException(
error.message || 'Failed to load menu',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
/*
@Get('full') @Get('full')
async getFullMenu() { async getFullMenu(@Headers('if-modified-since') ifModifiedSince?: string) {
console.log('Simplified endpoint called'); console.log('GET /menu/full requested');
return { test: 'OK' }; // Простейший ответ try {
} */ const result = await this.menuService.getFullMenuWithCache(ifModifiedSince);
if (!result.fresh && ifModifiedSince) {
throw new HttpException('Not Modified', HttpStatus.NOT_MODIFIED);
}
return result.menu;
} catch (error) {
if (error.status === HttpStatus.NOT_MODIFIED) {
throw error;
}
throw new HttpException(
error.message || 'Failed to load menu',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
/*
@Get('full')
async getFullMenu() {
console.log('Simplified endpoint called');
return { test: 'OK' }; // Простейший ответ
} */
@Get('check-updates') @Get('check-updates')
async checkUpdates(@Headers('if-modified-since') ifModifiedSince: string) { async checkUpdates(@Headers('if-modified-since') ifModifiedSince: string) {