argon2-utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1389 -0
- package/index.js +79 -0
- package/lib/src/app/app.config.ts +20 -0
- package/lib/src/app/app.routes.ts +30 -0
- package/lib/src/app/app.ts +15 -0
- package/lib/src/app/guards/admin.guard.ts +13 -0
- package/lib/src/app/guards/auth.guard.ts +13 -0
- package/lib/src/app/interceptors/auth.interceptor.ts +12 -0
- package/lib/src/app/pages/admin/admin.component.html +52 -0
- package/lib/src/app/pages/admin/admin.component.ts +86 -0
- package/lib/src/app/pages/applications/applications.component.html +72 -0
- package/lib/src/app/pages/applications/applications.component.ts +105 -0
- package/lib/src/app/pages/create-application/create-application.component.html +61 -0
- package/lib/src/app/pages/create-application/create-application.component.ts +67 -0
- package/lib/src/app/pages/login/login.component.html +38 -0
- package/lib/src/app/pages/login/login.component.ts +47 -0
- package/lib/src/app/pages/register/register.component.html +63 -0
- package/lib/src/app/pages/register/register.component.ts +56 -0
- package/lib/src/app/services/applications.service.ts +38 -0
- package/lib/src/app/services/auth.service.ts +61 -0
- package/lib/src/app/services/reviews.service.ts +23 -0
- package/lib/src/styles.scss +358 -0
- package/native/src/app.module.ts +16 -0
- package/native/src/applications/applications.controller.ts +47 -0
- package/native/src/applications/applications.module.ts +12 -0
- package/native/src/applications/applications.service.ts +71 -0
- package/native/src/auth/auth.controller.ts +26 -0
- package/native/src/auth/auth.module.ts +19 -0
- package/native/src/auth/auth.service.ts +62 -0
- package/native/src/auth/jwt-auth.guard.ts +8 -0
- package/native/src/auth/jwt.strategy.ts +20 -0
- package/native/src/database/database.module.ts +26 -0
- package/native/src/database/init.sql +45 -0
- package/native/src/main.ts +11 -0
- package/native/src/reviews/reviews.controller.ts +30 -0
- package/native/src/reviews/reviews.module.ts +12 -0
- package/native/src/reviews/reviews.service.ts +35 -0
- package/package.json +15 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { DatabaseModule } from './database/database.module';
|
|
3
|
+
import { AuthModule } from './auth/auth.module';
|
|
4
|
+
import { ApplicationsModule } from './applications/applications.module'; // МЕНЯТЬ: название модуля под свою тему
|
|
5
|
+
import { ReviewsModule } from './reviews/reviews.module'; // МЕНЯТЬ: название модуля под свою тему
|
|
6
|
+
|
|
7
|
+
// Корневой модуль — подключаем все модули сюда
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [
|
|
10
|
+
DatabaseModule, // подключение к БД (не менять)
|
|
11
|
+
AuthModule, // авторизация (не менять)
|
|
12
|
+
ApplicationsModule, // МЕНЯТЬ: своя предметная область
|
|
13
|
+
ReviewsModule, // МЕНЯТЬ: своя предметная область
|
|
14
|
+
],
|
|
15
|
+
})
|
|
16
|
+
export class AppModule {}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards, Request } from '@nestjs/common';
|
|
2
|
+
import { ApplicationsService } from './applications.service';
|
|
3
|
+
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
4
|
+
|
|
5
|
+
// МЕНЯТЬ: 'applications' на своё название маршрута (например 'orders')
|
|
6
|
+
// @UseGuards(JwtAuthGuard) — все роуты требуют авторизации
|
|
7
|
+
@UseGuards(JwtAuthGuard)
|
|
8
|
+
@Controller('applications')
|
|
9
|
+
export class ApplicationsController {
|
|
10
|
+
constructor(private readonly svc: ApplicationsService) {}
|
|
11
|
+
|
|
12
|
+
// POST /applications — создать запись
|
|
13
|
+
// МЕНЯТЬ: поля body под свою тему
|
|
14
|
+
@Post()
|
|
15
|
+
create(@Request() req, @Body() body: {
|
|
16
|
+
course_name: string; // МЕНЯТЬ
|
|
17
|
+
desired_start_date: string; // МЕНЯТЬ
|
|
18
|
+
payment_method: string; // МЕНЯТЬ
|
|
19
|
+
}) {
|
|
20
|
+
return this.svc.create(req.user.id, body); // req.user.id — id из JWT токена
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// GET /applications/my — мои записи (важно: /my должен быть выше /:id)
|
|
24
|
+
@Get('my')
|
|
25
|
+
findMy(@Request() req) {
|
|
26
|
+
return this.svc.findByUser(req.user.id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// GET /applications — все записи (для администратора)
|
|
30
|
+
@Get()
|
|
31
|
+
findAll(@Request() req) {
|
|
32
|
+
return this.svc.findAll();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// PATCH /applications/:id/status — сменить статус
|
|
36
|
+
// МЕНЯТЬ: 'status' если другое поле обновляется
|
|
37
|
+
@Patch(':id/status')
|
|
38
|
+
updateStatus(@Param('id') id: string, @Body() body: { status: string }, @Request() req) {
|
|
39
|
+
return this.svc.updateStatus(+id, body.status, req.user.is_admin);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// DELETE /applications/:id — удалить запись
|
|
43
|
+
@Delete(':id')
|
|
44
|
+
delete(@Param('id') id: string, @Request() req) {
|
|
45
|
+
return this.svc.delete(+id, req.user.id, req.user.is_admin);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { ApplicationsController } from './applications.controller';
|
|
3
|
+
import { ApplicationsService } from './applications.service';
|
|
4
|
+
|
|
5
|
+
// Модуль заявок — регистрирует контроллер и сервис
|
|
6
|
+
// МЕНЯТЬ: переименовать класс, контроллер и сервис под свою тему
|
|
7
|
+
// Не забыть добавить этот модуль в app.module.ts → imports
|
|
8
|
+
@Module({
|
|
9
|
+
controllers: [ApplicationsController],
|
|
10
|
+
providers: [ApplicationsService],
|
|
11
|
+
})
|
|
12
|
+
export class ApplicationsModule {}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Injectable, Inject, ForbiddenException, NotFoundException } from '@nestjs/common';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { DB_POOL } from '../database/database.module';
|
|
4
|
+
|
|
5
|
+
// МЕНЯТЬ: переименовать класс под свою тему (например OrdersService)
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class ApplicationsService {
|
|
8
|
+
constructor(@Inject(DB_POOL) private readonly pool: Pool) {}
|
|
9
|
+
|
|
10
|
+
// CREATE — создать новую запись
|
|
11
|
+
// МЕНЯТЬ: поля dto, таблицу applications, колонки INSERT
|
|
12
|
+
async create(userId: number, dto: {
|
|
13
|
+
course_name: string; // МЕНЯТЬ: своё поле
|
|
14
|
+
desired_start_date: string; // МЕНЯТЬ: своё поле
|
|
15
|
+
payment_method: string; // МЕНЯТЬ: своё поле
|
|
16
|
+
}) {
|
|
17
|
+
const result = await this.pool.query(
|
|
18
|
+
`INSERT INTO applications (user_id, course_name, desired_start_date, payment_method)
|
|
19
|
+
VALUES ($1, $2, $3, $4) RETURNING *`,
|
|
20
|
+
[userId, dto.course_name, dto.desired_start_date, dto.payment_method],
|
|
21
|
+
);
|
|
22
|
+
return result.rows[0];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// READ — получить записи текущего пользователя (с отзывом через LEFT JOIN)
|
|
26
|
+
// МЕНЯТЬ: таблицу applications, поля SELECT
|
|
27
|
+
async findByUser(userId: number) {
|
|
28
|
+
const result = await this.pool.query(
|
|
29
|
+
`SELECT a.*, r.review_text, r.id as review_id
|
|
30
|
+
FROM applications a
|
|
31
|
+
LEFT JOIN reviews r ON r.application_id = a.id
|
|
32
|
+
WHERE a.user_id = $1
|
|
33
|
+
ORDER BY a.created_at DESC`,
|
|
34
|
+
[userId],
|
|
35
|
+
);
|
|
36
|
+
return result.rows;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// READ — получить все записи (для администратора)
|
|
40
|
+
// МЕНЯТЬ: таблицу applications, поля SELECT
|
|
41
|
+
async findAll() {
|
|
42
|
+
const result = await this.pool.query(
|
|
43
|
+
`SELECT a.*, u.login, u.full_name, u.email, u.phone
|
|
44
|
+
FROM applications a
|
|
45
|
+
JOIN users u ON u.id = a.user_id
|
|
46
|
+
ORDER BY a.created_at DESC`,
|
|
47
|
+
);
|
|
48
|
+
return result.rows;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// UPDATE — сменить статус записи (только администратор)
|
|
52
|
+
// МЕНЯТЬ: таблицу applications, поле status
|
|
53
|
+
async updateStatus(id: number, status: string, isAdmin: boolean) {
|
|
54
|
+
if (!isAdmin) throw new ForbiddenException('Только для администратора');
|
|
55
|
+
const result = await this.pool.query(
|
|
56
|
+
`UPDATE applications SET status = $1 WHERE id = $2 RETURNING *`,
|
|
57
|
+
[status, id],
|
|
58
|
+
);
|
|
59
|
+
if (result.rows.length === 0) throw new NotFoundException('Запись не найдена');
|
|
60
|
+
return result.rows[0];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// DELETE — удалить запись
|
|
64
|
+
async delete(id: number, userId: number, isAdmin: boolean) {
|
|
65
|
+
const check = await this.pool.query('SELECT * FROM applications WHERE id = $1', [id]);
|
|
66
|
+
if (check.rows.length === 0) throw new NotFoundException('Запись не найдена');
|
|
67
|
+
if (!isAdmin && check.rows[0].user_id !== userId) throw new ForbiddenException('Нет доступа');
|
|
68
|
+
await this.pool.query('DELETE FROM applications WHERE id = $1', [id]);
|
|
69
|
+
return { success: true };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Controller, Post, Body } from '@nestjs/common';
|
|
2
|
+
import { AuthService } from './auth.service';
|
|
3
|
+
|
|
4
|
+
// Роуты: POST /auth/register и POST /auth/login
|
|
5
|
+
@Controller('auth')
|
|
6
|
+
export class AuthController {
|
|
7
|
+
constructor(private readonly authService: AuthService) {}
|
|
8
|
+
|
|
9
|
+
// МЕНЯТЬ: поля body если другие поля при регистрации
|
|
10
|
+
@Post('register')
|
|
11
|
+
register(@Body() body: {
|
|
12
|
+
login: string;
|
|
13
|
+
password: string;
|
|
14
|
+
full_name: string; // МЕНЯТЬ
|
|
15
|
+
phone: string; // МЕНЯТЬ
|
|
16
|
+
email: string; // МЕНЯТЬ
|
|
17
|
+
}) {
|
|
18
|
+
return this.authService.register(body);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Не менять
|
|
22
|
+
@Post('login')
|
|
23
|
+
login(@Body() body: { login: string; password: string }) {
|
|
24
|
+
return this.authService.login(body.login, body.password);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { JwtModule } from '@nestjs/jwt';
|
|
3
|
+
import { AuthController } from './auth.controller';
|
|
4
|
+
import { AuthService } from './auth.service';
|
|
5
|
+
import { JwtStrategy } from './jwt.strategy';
|
|
6
|
+
|
|
7
|
+
// Модуль авторизации — не менять
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [
|
|
10
|
+
JwtModule.register({
|
|
11
|
+
secret: process.env.JWT_SECRET || 'supersecretkey123', // секрет в .env
|
|
12
|
+
signOptions: { expiresIn: '8h' }, // токен живёт 8 часов
|
|
13
|
+
}),
|
|
14
|
+
],
|
|
15
|
+
controllers: [AuthController],
|
|
16
|
+
providers: [AuthService, JwtStrategy],
|
|
17
|
+
exports: [AuthService],
|
|
18
|
+
})
|
|
19
|
+
export class AuthModule {}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Injectable, UnauthorizedException, ConflictException, Inject } from '@nestjs/common';
|
|
2
|
+
import { JwtService } from '@nestjs/jwt';
|
|
3
|
+
import { Pool } from 'pg';
|
|
4
|
+
import * as bcrypt from 'bcryptjs';
|
|
5
|
+
import { DB_POOL } from '../database/database.module';
|
|
6
|
+
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class AuthService {
|
|
9
|
+
constructor(
|
|
10
|
+
@Inject(DB_POOL) private readonly pool: Pool, // инжектируем пул БД
|
|
11
|
+
private readonly jwtService: JwtService,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
// Регистрация нового пользователя
|
|
15
|
+
// МЕНЯТЬ: поля dto если у тебя другие поля в таблице users
|
|
16
|
+
async register(dto: {
|
|
17
|
+
login: string;
|
|
18
|
+
password: string;
|
|
19
|
+
full_name: string; // МЕНЯТЬ: своё поле
|
|
20
|
+
phone: string; // МЕНЯТЬ: своё поле
|
|
21
|
+
email: string; // МЕНЯТЬ: своё поле
|
|
22
|
+
}) {
|
|
23
|
+
// Проверяем что логин не занят
|
|
24
|
+
const exists = await this.pool.query('SELECT id FROM users WHERE login = $1', [dto.login]);
|
|
25
|
+
if (exists.rows.length > 0) {
|
|
26
|
+
throw new ConflictException('Пользователь с таким логином уже существует');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Хешируем пароль (10 — сложность хеширования)
|
|
30
|
+
const hash = await bcrypt.hash(dto.password, 10);
|
|
31
|
+
|
|
32
|
+
// МЕНЯТЬ: поля INSERT и VALUES под свою таблицу users
|
|
33
|
+
const result = await this.pool.query(
|
|
34
|
+
`INSERT INTO users (login, password_hash, full_name, phone, email)
|
|
35
|
+
VALUES ($1, $2, $3, $4, $5) RETURNING id, login, full_name, email, is_admin`,
|
|
36
|
+
[dto.login, hash, dto.full_name, dto.phone, dto.email],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const user = result.rows[0];
|
|
40
|
+
// Создаём JWT токен с данными пользователя
|
|
41
|
+
const token = this.jwtService.sign({ id: user.id, login: user.login, is_admin: user.is_admin });
|
|
42
|
+
return { token, user };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Вход в систему
|
|
46
|
+
async login(login: string, password: string) {
|
|
47
|
+
const result = await this.pool.query('SELECT * FROM users WHERE login = $1', [login]);
|
|
48
|
+
const user = result.rows[0];
|
|
49
|
+
|
|
50
|
+
if (!user) throw new UnauthorizedException('Неверный логин или пароль');
|
|
51
|
+
|
|
52
|
+
// Сравниваем пароль с хешем в БД
|
|
53
|
+
const valid = await bcrypt.compare(password, user.password_hash);
|
|
54
|
+
if (!valid) throw new UnauthorizedException('Неверный логин или пароль');
|
|
55
|
+
|
|
56
|
+
const token = this.jwtService.sign({ id: user.id, login: user.login, is_admin: user.is_admin });
|
|
57
|
+
return {
|
|
58
|
+
token,
|
|
59
|
+
user: { id: user.id, login: user.login, full_name: user.full_name, is_admin: user.is_admin },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { AuthGuard } from '@nestjs/passport';
|
|
3
|
+
|
|
4
|
+
// Guard для проверки JWT-токена на бэкенде
|
|
5
|
+
// Используется как @UseGuards(JwtAuthGuard) над контроллером или отдельным роутом
|
|
6
|
+
// Не менять — работает автоматически через passport-jwt
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { PassportStrategy } from '@nestjs/passport';
|
|
3
|
+
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
4
|
+
|
|
5
|
+
// Стратегия JWT — читает токен из заголовка Authorization: Bearer <token>
|
|
6
|
+
// Не менять
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
9
|
+
constructor() {
|
|
10
|
+
super({
|
|
11
|
+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
12
|
+
secretOrKey: process.env.JWT_SECRET || 'supersecretkey123',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Возвращает payload токена — доступен через @Request() req как req.user
|
|
17
|
+
async validate(payload: { id: number; login: string; is_admin: boolean }) {
|
|
18
|
+
return payload;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Module, Global } from '@nestjs/common';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
|
|
4
|
+
export const DB_POOL = 'DB_POOL'; // токен для инжекции пула в сервисы
|
|
5
|
+
|
|
6
|
+
// Создаём пул подключений к PostgreSQL из .env файла
|
|
7
|
+
const dbProvider = {
|
|
8
|
+
provide: DB_POOL,
|
|
9
|
+
useFactory: () => {
|
|
10
|
+
return new Pool({
|
|
11
|
+
host: process.env.DB_HOST || 'localhost',
|
|
12
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
13
|
+
user: process.env.DB_USER || 'postgres',
|
|
14
|
+
password: process.env.DB_PASSWORD || 'postgres',
|
|
15
|
+
database: process.env.DB_NAME || 'examen_db', // МЕНЯТЬ: имя БД в .env
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// @Global() — модуль доступен во всём приложении без повторного импорта
|
|
21
|
+
@Global()
|
|
22
|
+
@Module({
|
|
23
|
+
providers: [dbProvider],
|
|
24
|
+
exports: [dbProvider],
|
|
25
|
+
})
|
|
26
|
+
export class DatabaseModule {}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
-- =====================================================
|
|
2
|
+
-- СКРИПТ СОЗДАНИЯ ТАБЛИЦ
|
|
3
|
+
-- Запустить ОДИН РАЗ в pgAdmin → Query Tool
|
|
4
|
+
-- Или через: psql -U postgres -d имя_бд -f init.sql
|
|
5
|
+
-- =====================================================
|
|
6
|
+
|
|
7
|
+
-- Таблица пользователей — НЕ МЕНЯТЬ структуру (login, password_hash нужны для авторизации)
|
|
8
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
9
|
+
id SERIAL PRIMARY KEY,
|
|
10
|
+
login VARCHAR(50) UNIQUE NOT NULL, -- логин для входа (латиница + цифры)
|
|
11
|
+
password_hash VARCHAR(255) NOT NULL, -- bcrypt-хэш пароля
|
|
12
|
+
full_name VARCHAR(200) NOT NULL, -- ФИО пользователя
|
|
13
|
+
phone VARCHAR(20) NOT NULL, -- телефон формата 8(XXX)XXX-XX-XX
|
|
14
|
+
email VARCHAR(100) NOT NULL, -- email
|
|
15
|
+
is_admin BOOLEAN DEFAULT FALSE, -- флаг администратора
|
|
16
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
-- МЕНЯТЬ: 'applications' → своё название таблицы
|
|
20
|
+
-- МЕНЯТЬ: поля course_name, desired_start_date, payment_method → свои поля
|
|
21
|
+
-- МЕНЯТЬ: CHECK (payment_method IN ('cash', 'transfer')) → свои значения
|
|
22
|
+
-- МЕНЯТЬ: CHECK (status IN ('new', 'in_progress', 'completed')) → свои статусы
|
|
23
|
+
CREATE TABLE IF NOT EXISTS applications (
|
|
24
|
+
id SERIAL PRIMARY KEY,
|
|
25
|
+
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, -- НЕ МЕНЯТЬ
|
|
26
|
+
course_name VARCHAR(200) NOT NULL, -- МЕНЯТЬ: своё поле
|
|
27
|
+
desired_start_date DATE NOT NULL, -- МЕНЯТЬ: тип DATE или VARCHAR
|
|
28
|
+
payment_method VARCHAR(20) NOT NULL CHECK (payment_method IN ('cash', 'transfer')), -- МЕНЯТЬ
|
|
29
|
+
status VARCHAR(30) DEFAULT 'new' CHECK (status IN ('new', 'in_progress', 'completed')), -- МЕНЯТЬ статусы
|
|
30
|
+
created_at TIMESTAMP DEFAULT NOW() -- НЕ МЕНЯТЬ
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
-- МЕНЯТЬ: 'reviews' → своё название таблицы (comments, feedback и т.д.)
|
|
34
|
+
-- МЕНЯТЬ: 'review_text TEXT' → своё поле
|
|
35
|
+
-- МЕНЯТЬ: ссылку application_id на свою таблицу заявок
|
|
36
|
+
CREATE TABLE IF NOT EXISTS reviews (
|
|
37
|
+
id SERIAL PRIMARY KEY,
|
|
38
|
+
application_id INTEGER REFERENCES applications(id) ON DELETE CASCADE, -- МЕНЯТЬ имя таблицы
|
|
39
|
+
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, -- НЕ МЕНЯТЬ
|
|
40
|
+
review_text TEXT NOT NULL, -- МЕНЯТЬ имя поля
|
|
41
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
-- После создания таблиц запустить: node create-admin.js
|
|
45
|
+
-- Это создаст администратора login=Admin, password=KorokNET
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NestFactory } from '@nestjs/core';
|
|
2
|
+
import { AppModule } from './app.module';
|
|
3
|
+
import * as dotenv from 'dotenv';
|
|
4
|
+
dotenv.config(); // загружает .env файл
|
|
5
|
+
|
|
6
|
+
async function bootstrap() {
|
|
7
|
+
const app = await NestFactory.create(AppModule);
|
|
8
|
+
app.enableCors({ origin: 'http://localhost:4200' }); // МЕНЯТЬ: порт фронтенда
|
|
9
|
+
await app.listen(process.env.PORT || 3000); // порт бэкенда в .env
|
|
10
|
+
}
|
|
11
|
+
bootstrap();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Controller, Post, Get, Delete, Body, Param, UseGuards, Request } from '@nestjs/common';
|
|
2
|
+
import { ReviewsService } from './reviews.service';
|
|
3
|
+
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
4
|
+
|
|
5
|
+
// МЕНЯТЬ: 'reviews' на своё название (например 'comments', 'feedback')
|
|
6
|
+
// @UseGuards(JwtAuthGuard) — все роуты требуют авторизации
|
|
7
|
+
@UseGuards(JwtAuthGuard)
|
|
8
|
+
@Controller('reviews')
|
|
9
|
+
export class ReviewsController {
|
|
10
|
+
constructor(private readonly svc: ReviewsService) {}
|
|
11
|
+
|
|
12
|
+
// POST /reviews — создать отзыв (авторизованный пользователь)
|
|
13
|
+
// МЕНЯТЬ: поля body под свою тему
|
|
14
|
+
@Post()
|
|
15
|
+
create(@Request() req, @Body() body: { application_id: number; review_text: string }) {
|
|
16
|
+
return this.svc.create(req.user.id, body); // req.user.id — из JWT-токена
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// GET /reviews/:applicationId — получить отзывы по заявке
|
|
20
|
+
@Get(':applicationId')
|
|
21
|
+
findByApplication(@Param('applicationId') id: string) {
|
|
22
|
+
return this.svc.findByApplication(+id); // +id конвертирует строку в число
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// DELETE /reviews/:id — удалить отзыв
|
|
26
|
+
@Delete(':id')
|
|
27
|
+
delete(@Param('id') id: string) {
|
|
28
|
+
return this.svc.delete(+id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { ReviewsController } from './reviews.controller';
|
|
3
|
+
import { ReviewsService } from './reviews.service';
|
|
4
|
+
|
|
5
|
+
// Модуль отзывов — регистрирует контроллер и сервис
|
|
6
|
+
// МЕНЯТЬ: переименовать класс, контроллер и сервис под свою тему
|
|
7
|
+
// Не забыть добавить этот модуль в app.module.ts → imports
|
|
8
|
+
@Module({
|
|
9
|
+
controllers: [ReviewsController],
|
|
10
|
+
providers: [ReviewsService],
|
|
11
|
+
})
|
|
12
|
+
export class ReviewsModule {}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { DB_POOL } from '../database/database.module';
|
|
4
|
+
|
|
5
|
+
// МЕНЯТЬ: переименовать под свою тему (например CommentsService, FeedbackService)
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class ReviewsService {
|
|
8
|
+
constructor(@Inject(DB_POOL) private readonly pool: Pool) {}
|
|
9
|
+
|
|
10
|
+
// CREATE — оставить отзыв
|
|
11
|
+
// МЕНЯТЬ: таблицу reviews, поля INSERT
|
|
12
|
+
async create(userId: number, dto: { application_id: number; review_text: string }) {
|
|
13
|
+
const result = await this.pool.query(
|
|
14
|
+
`INSERT INTO reviews (application_id, user_id, review_text)
|
|
15
|
+
VALUES ($1, $2, $3) RETURNING *`,
|
|
16
|
+
[dto.application_id, userId, dto.review_text],
|
|
17
|
+
);
|
|
18
|
+
return result.rows[0];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// READ — получить отзывы по записи
|
|
22
|
+
async findByApplication(applicationId: number) {
|
|
23
|
+
const result = await this.pool.query(
|
|
24
|
+
'SELECT * FROM reviews WHERE application_id = $1',
|
|
25
|
+
[applicationId],
|
|
26
|
+
);
|
|
27
|
+
return result.rows;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// DELETE — удалить отзыв
|
|
31
|
+
async delete(id: number) {
|
|
32
|
+
await this.pool.query('DELETE FROM reviews WHERE id = $1', [id]);
|
|
33
|
+
return { success: true };
|
|
34
|
+
}
|
|
35
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "argon2-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Argon2 hashing utilities for Node.js",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"argon2",
|
|
9
|
+
"hashing",
|
|
10
|
+
"crypto",
|
|
11
|
+
"password"
|
|
12
|
+
],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "MIT"
|
|
15
|
+
}
|