Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
"@repo/api": "workspace:*",
"dotenv": "^17.3.1",
"escape-html": "^1.0.3",
"nestjs-zod": "^5.1.1",
"pg": "^8.18.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2"
"rxjs": "^7.8.2",
"zod": "^4.3.6"
},
"devDependencies": {
"@nestjs/cli": "^11.0.14",
Expand Down
6 changes: 3 additions & 3 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ enum Priority {
}

model User {
id String @id @default(cuid())
id String @id @default(uuid())
email String @unique
name String?
password String
Expand All @@ -39,7 +39,7 @@ model User {
}

model Project {
id String @id @default(cuid())
id String @id @default(uuid())
name String
description String?
createdById String
Expand All @@ -50,7 +50,7 @@ model Project {
}

model Task {
id String @id @default(cuid())
id String @id @default(uuid())
title String
description String?
status TaskStatus @default(TODO)
Expand Down
9 changes: 8 additions & 1 deletion apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { Module } from '@nestjs/common'
import { APP_PIPE, APP_INTERCEPTOR } from '@nestjs/core'
import { ZodSerializerInterceptor } from 'nestjs-zod'

import { AppService } from './app.service'
import { AppController } from './app.controller'

import { ConfigModule } from '@nestjs/config'
import { PrismaModule } from '../prisma/prisma.module'
import { AuthModule } from './auth/auth.module'
import { CustomZodValidationPipe } from './common/providers/zod-validation.provider'

@Module({
imports: [ConfigModule.forRoot({ isGlobal: true }), PrismaModule, AuthModule],
controllers: [AppController],
providers: [AppService],
providers: [
AppService,
{ provide: APP_PIPE, useClass: CustomZodValidationPipe },
{ provide: APP_INTERCEPTOR, useClass: ZodSerializerInterceptor },
],
})
export class AppModule {}
9 changes: 7 additions & 2 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'
import { AuthService } from './auth.service'
import { RegisterRequest } from '@repo/api'
import { RegisterRequestDto } from './dto/register.dto'
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'

@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}

@Post('register')
@HttpCode(HttpStatus.CREATED)
async register(@Body() dto: RegisterRequest) {
@ApiOperation({ summary: 'Регистрация нового пользователя' })
@ApiResponse({ status: 201, description: 'Пользователь успешно зарегистрирован' })
@ApiResponse({ status: 400, description: 'Некорректные данные' })
async register(@Body() dto: RegisterRequestDto) {
return this.authService.register(dto)
}
}
4 changes: 2 additions & 2 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ConflictException, Injectable } from '@nestjs/common'
import { RegisterRequest } from '@repo/api'
import { RegisterRequestDto } from './dto/register.dto'
import { PrismaService } from '../../prisma/prisma.service'

@Injectable()
export class AuthService {
constructor(private readonly prismaService: PrismaService) {}

async register(dto: RegisterRequest) {
async register(dto: RegisterRequestDto) {
const { name, email, password } = dto

const existUser = await this.prismaService.user.findUnique({
Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/auth/dto/register.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createZodDto } from 'nestjs-zod'
import { registerRequestSchema } from '@repo/api'

// Создаём DTO класс для NestJS и Swagger из схемы
export class RegisterRequestDto extends createZodDto(registerRequestSchema) {}
18 changes: 18 additions & 0 deletions apps/api/src/common/providers/zod-validation.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { BadRequestException } from '@nestjs/common'
import { createZodValidationPipe } from 'nestjs-zod'

export const CustomZodValidationPipe = createZodValidationPipe({
createValidationException: (error: any) => {
const zodError = error as any
const errors = zodError.issues

const paths = errors.map((err: any) => err.path.join('.'))
const messages = errors.map((err: any) => err.message)

return new BadRequestException({
statusCode: 400,
path: paths,
message: messages,
})
},
})
3 changes: 2 additions & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core'

import { AppModule } from './app.module'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { cleanupOpenApiDoc } from 'nestjs-zod'

async function bootstrap() {
const app = await NestFactory.create(AppModule)
Expand All @@ -16,7 +17,7 @@ async function bootstrap() {

const document = SwaggerModule.createDocument(app, config)

SwaggerModule.setup('api/docs', app, document)
SwaggerModule.setup('api/docs', app, cleanupOpenApiDoc(document))

await app.listen(4000)
}
Expand Down
29 changes: 29 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,32 @@
- Type-safety (строгий TypeScript)
- Unit тесты для критичной логики
- FSD линтер для Frontend

---

## 📖 Документация проекта

### Валидация данных

- [Общая валидация (Zod)](./validation/README.md) - Концепция и архитектура
- [Настройка валидации](./validation/setup.md) - Пошаговая инструкция
- [Backend валидация](./backend/validation/README.md) - NestJS + Zod
- [Кастомные ошибки](./backend/validation/custom-errors.md) - Формат ошибок

### Backend

- [Prisma](./backend/prisma/README.md) - База данных
- [Swagger API](./backend/swagger/README.md) - Документация API
- [E2E тесты](./backend/test/e2e-connection-test.md) - Тестирование

### Frontend

- [ShadCN UI](./frontend/shadcn.md) - Компоненты UI

### Рабочие процессы

- [Начало работы](./work/start.md) - С чего начать
- [Git](./work/git.md) - Работа с Git
- [Git Hooks](./work/git-hooks.md) - Автоматизация проверок
- [Feature Flags](./work/feature-flags.md) - Управление фичами
- [Общие практики](./work/common.md) - Best practices
52 changes: 52 additions & 0 deletions docs/backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Backend Документация

## 📚 Содержание

### Валидация

- [Валидация данных](./validation/README.md) - NestJS + Zod
- [Кастомные ошибки](./validation/custom-errors.md) - Формат ошибок

### API

- [Swagger](./swagger/README.md) - API документация

### База данных

- [Prisma](./prisma/README.md) - ORM и миграции

### Тестирование

- [E2E тесты](./test/e2e-connection-test.md) - Интеграционные тесты

---

## Быстрый старт

1. **Настройка валидации** - [validation/setup](../validation/setup.md)
2. **Swagger документация** - http://localhost:4000/api/docs
3. **Prisma Studio** - `pnpm prisma:studio`

## Структура Backend

```
apps/api/src/
├── auth/ # Аутентификация
├── common/ # Общие провайдеры и утилиты
│ └── providers/
│ └── zod-validation.provider.ts
├── app.module.ts # Главный модуль
└── main.ts # Точка входа

apps/api/prisma/
├── schema.prisma # Схема БД
└── migrations/ # Миграции
```

## Технологии

- **Framework**: NestJS
- **Validation**: Zod + nestjs-zod
- **Database**: PostgreSQL + Prisma
- **API Docs**: Swagger
- **Testing**: Vitest
66 changes: 61 additions & 5 deletions docs/backend/swagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ http://localhost:4000/api/docs

```bash
cd apps/api
pnpm add @nestjs/swagger
pnpm add @nestjs/swagger nestjs-zod
```

или установить все зависимости проекта(установит только те которые ещё не установлены):
или установить все зависимости проекта (установит только те которые ещё не установлены):

```bash
pnpm install
Expand All @@ -29,6 +29,7 @@ Swagger настраивается в файле `apps/api/src/main.ts`:

```typescript
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { cleanupOpenApiDoc } from 'nestjs-zod'

const config = new DocumentBuilder()
.setTitle('Tracker Task API')
Expand All @@ -38,17 +39,72 @@ const config = new DocumentBuilder()
.build()

const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('api/docs', app, document)

// Очистка для корректной работы с Zod схемами
SwaggerModule.setup('api/docs', app, cleanupOpenApiDoc(document))
```

## Интеграция с Zod

### Автоматическая генерация схем

DTO классы, созданные через `createZodDto`, автоматически генерируют OpenAPI схемы:

```typescript
// packages/api/src/auth/dto/register.dto.ts
export const registerRequestSchema = z.object({
email: z.email({ error: 'Email некорректный' }),
password: z.string().min(6),
})

// apps/api/src/auth/dto/register.dto.ts
export class RegisterRequestDto extends createZodDto(registerRequestSchema) {}
```

Swagger автоматически покажет:

- Тип поля (`string`, `number`, и т.д.)
- Валидационные правила (`minLength`, `format: email`)
- Обязательность полей
- Примеры значений

### Документирование эндпоинтов

```typescript
@ApiTags('auth')
@Controller('auth')
export class AuthController {
@Post('register')
@ApiOperation({ summary: 'Регистрация нового пользователя' })
@ApiResponse({ status: 201, description: 'Успешная регистрация' })
@ApiResponse({ status: 400, description: 'Ошибка валидации' })
async register(@Body() dto: RegisterRequestDto) {
return this.authService.register(dto)
}
}
```

### cleanupOpenApiDoc

**Важно!** Функция `cleanupOpenApiDoc` необходима для корректной работы Zod схем в Swagger:

```typescript
SwaggerModule.setup('api/docs', app, cleanupOpenApiDoc(document))
```

Без неё Swagger может показывать некорректные схемы.

## Что это дает?

- 📝 Автоматическая генерация документации API
- 📝 Автоматическая генерация документации из Zod схем
- 🧪 Возможность тестировать эндпоинты прямо из браузера
- 🔒 Поддержка Bearer Auth токенов
- 📊 Интеактивная документация с примерами запросов и ответов
- 📊 Интерактивная документация с примерами запросов и ответов
- ✅ Синхронизация валидации и документации

## Полезные ссылки

- [Официальная документация NestJS Swagger](https://docs.nestjs.com/openapi/introduction)
- [nestjs-zod OpenAPI поддержка](https://github.com/risenforces/nestjs-zod#openapi-support-swagger)
- [Swagger UI](https://swagger.io/tools/swagger-ui/)
- [Валидация в проекте](../validation/README.md)
Loading