This commit is contained in:
2026-07-02 15:59:46 +03:30
parent a8a6f085d0
commit dcd8455106
5 changed files with 811 additions and 0 deletions
+219
View File
@@ -0,0 +1,219 @@
# API Conventions
HTTP API design standards for Negareh backend. Swagger documentation is available at `/docs` when the server is running.
## Base URL
No global path prefix. Routes encode the audience and resource directly:
```
POST /public/auth/otp/request
GET /admin/orders
GET /public/orders/:id
```
## Route naming
### Audience prefixes
| Prefix | Description |
|--------|-------------|
| `public/` | Customer-facing endpoints |
| `admin/` | Back-office endpoints |
Auth endpoints embed the audience in the path: `public/auth/...`, `admin/auth/...`.
### HTTP methods
| Method | Usage |
|--------|-------|
| `GET` | Read single resource or list |
| `POST` | Create resource or action (e.g. OTP request) |
| `PATCH` | Partial update |
| `PUT` | Full replace (rare) |
| `DELETE` | Soft or hard delete |
### Path style
- Use **plural nouns** for collections: `/admin/orders`, `/public/tickets`.
- Use **path parameters** for IDs: `/admin/orders/:id`.
- Use **query parameters** for filtering, sorting, and pagination.
- Nest related actions under the resource: `/admin/orders/:id/assign-designer`.
Controllers use `@Controller()` with the full path on each method (no shared controller prefix), except where a module uses a short prefix like `@Controller('admin')`.
## Request format
### Headers
| Header | Required | Description |
|--------|----------|-------------|
| `Content-Type: application/json` | Yes (JSON bodies) | Standard JSON requests |
| `Authorization: Bearer <token>` | Protected routes | JWT access token |
### Validation
All inputs are validated through DTO classes with `class-validator`. The global `ValidationPipe` has `transform: true`, so query strings are coerced to the correct types.
Invalid input returns `400` with validation messages in the error envelope.
### File uploads
Multipart uploads use `@fastify/multipart`. Upload endpoints live in the `uploader` module and return file metadata/URLs from S3.
## Response format
### Success (single resource)
```json
{
"statusCode": 200,
"success": true,
"data": {
"id": "01HXYZ...",
"phone": "09362532122"
}
}
```
### Success (paginated list)
Services return `PaginatedResult<T>`; the interceptor wraps it:
```json
{
"statusCode": 200,
"success": true,
"data": [
{ "id": "01HXYZ...", "status": "pending" }
],
"meta": {
"total": 42,
"page": 1,
"limit": 10,
"totalPages": 5
}
}
```
### Pagination query parameters
Common fields on list DTOs:
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `page` | number | `1` | Page number (1-based) |
| `limit` | number | `10` | Items per page |
| `search` | string | — | Free-text search |
| `orderBy` | string | `createdAt` | Sort field |
| `order` | `asc` \| `desc` | `desc` | Sort direction |
Some endpoints may use cursor-based pagination and return `nextCursor` at the top level.
### Error
```json
{
"statusCode": 400,
"success": false,
"error": {
"message": ["Phone number is invalid"]
}
}
```
`message` is always an array of strings, even for a single error.
### HTTP status codes
| Code | When |
|------|------|
| `200` | Successful GET, PATCH, DELETE |
| `201` | Successful POST (create) — when explicitly set |
| `400` | Validation failure, bad input |
| `401` | Missing or invalid token |
| `403` | Authenticated but insufficient permissions |
| `404` | Resource not found |
| `429` | Rate limit exceeded |
| `500` | Unexpected server error |
## Swagger
- Title: **Negareh API**
- Path: `/docs`
- Bearer auth scheme enabled — use the Authorize button to set JWT.
- Controllers document endpoints with `@ApiTags`, `@ApiOperation`, `@ApiBody`, `@ApiBearerAuth`.
- DTO fields use `@ApiProperty` for examples and descriptions.
## Controller pattern
```typescript
@ApiTags('orders')
@ApiBearerAuth()
@Controller()
export class OrderController {
constructor(private readonly orderService: OrderService) {}
// User route
@Get('public/orders')
@UseGuards(AuthGuard)
@ApiOperation({ summary: 'Get all orders with pagination and filters' })
findAll(@Query() dto: FindOrdersDto, @UserId() userId: string) {
return this.orderService.findOrdersAsUser(userId, dto);
}
// Admin route
@Post('admin/orders')
@UseGuards(AdminAuthGuard)
@Permissions(PermissionEnum.CREATE_ORDER)
@ApiOperation({ summary: 'Create order' })
create(@AdminId() adminId: string, @Body() body: CreateOrderAsAdminDto) {
return this.orderService.createOrderAsAdmin(adminId, body);
}
}
```
## Identifiers
- Resource IDs are **ULIDs** (26-character strings).
- Pass IDs as path or body fields; do not use integer IDs.
## Dates and times
- Stored as `timestamptz` in UTC.
- Serialized as ISO 8601 strings in JSON responses.
- Token `expire` fields use Unix milliseconds.
## CORS
CORS is enabled for all origins in development with credentials support:
- Methods: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`
- `credentials: true`
Tighten `origin` in production deployments.
## Rate limiting
Global default: **5 requests per 60 seconds** per IP (`ThrottlerModule`).
Stricter limits on sensitive routes:
- OTP request: 3 per 180 seconds
- Refresh token: custom decorator (`@RefreshTokenRateLimit()`)
Exceeded limits return `429 Too Many Requests`.
## Versioning
The API is currently unversioned (`v1` is implicit). Breaking changes should be coordinated with all clients (admin console, mobile app) before deployment.
## Adding a new endpoint checklist
1. Create or extend a DTO with validation and Swagger decorators.
2. Add a thin controller method with correct `public/` or `admin/` path.
3. Apply the appropriate guard and `@Permissions` if admin-only.
4. Implement logic in the service; use repository for queries.
5. Return typed data — the response interceptor handles wrapping.
6. Document with `@ApiOperation` and related Swagger decorators.
7. Add migration if new entities or columns are required.
+163
View File
@@ -0,0 +1,163 @@
# Architecture
Negareh API is a NestJS backend built on **Fastify**, **PostgreSQL** (via MikroORM), and **Redis** (caching, OTP, queues). It powers both the customer-facing app and the admin console.
## Tech stack
| Layer | Technology |
|-------|------------|
| Runtime | Node.js, TypeScript |
| Framework | NestJS 11 |
| HTTP server | Fastify (`@nestjs/platform-fastify`) |
| ORM | MikroORM 6 (PostgreSQL driver) |
| Auth | JWT + refresh tokens, OTP via SMS |
| Cache / OTP | Redis (`@keyv/redis`, `CacheService`) |
| Jobs | BullMQ (`@nestjs/bullmq`) |
| Real-time | Socket.IO (notifications) |
| File storage | AWS S3-compatible bucket |
| Payments | Zarinpal gateway |
| API docs | Swagger at `/docs` |
## High-level layout
```
src/
├── main.ts # Bootstrap: Fastify, pipes, interceptors, CORS, Swagger
├── app.module.ts # Root module — wires all feature modules
├── config/ # MikroORM, cache, HTTP, Swagger
├── core/ # Cross-cutting: exceptions, interceptors, middlewares
├── common/ # Shared entities, decorators, enums, interfaces
├── modules/ # Feature modules (domain boundaries)
└── seeders/ # Database seed data
database/
└── migrations/ # Versioned schema changes (MikroORM migrations)
```
## Feature modules
Each domain lives under `src/modules/<name>/` with a consistent internal structure:
```
modules/<feature>/
├── <feature>.module.ts
├── controllers/ # HTTP layer (thin)
├── providers/ # Business logic (services)
├── repositories/ # Data access (extends EntityRepository)
├── entities/ # MikroORM entities
├── dto/ # Request/response validation
├── listeners/ # Event handlers (@OnEvent)
├── events/ # Domain events (EventEmitter2)
└── enum/ # Feature-specific enums
```
Current modules:
- **auth** — OTP login, JWT, refresh tokens
- **user** — End-user accounts and credit
- **admin** — Admin accounts
- **roles** — Roles and permissions (RBAC)
- **product** — Products and categories
- **form-builder** — Dynamic product fields and options
- **request** — Customer requests / quotes
- **invoice** — Invoicing
- **order** — Order lifecycle
- **payment** — Online payments (Zarinpal)
- **print** — Print-related admin operations
- **ticket** — Support tickets
- **chat** — Order/chat messaging
- **notification** — SMS, push, WebSocket notifications
- **uploader** — File uploads to S3
- **announcements**, **learnings**, **criticisms** — Content and feedback
- **util** — Shared utilities (cache, phone normalization)
## Request lifecycle
1. **Fastify** receives the HTTP request.
2. **ValidationPipe** validates and transforms DTOs (`class-validator` + `class-transformer`).
3. **Guards** enforce authentication (`AuthGuard`, `AdminAuthGuard`) and permissions (`@Permissions`).
4. **Controller** delegates to a **service**.
5. **Service** uses **repositories** and/or `EntityManager` for persistence.
6. **ResponseInterceptor** wraps successful responses in a standard envelope.
7. **HttpExceptionFilter** formats error responses.
## Cross-cutting concerns
### Response envelope
Successful responses:
```json
{
"statusCode": 200,
"success": true,
"data": { }
}
```
Paginated responses also include `meta`:
```json
{
"statusCode": 200,
"success": true,
"data": [],
"meta": { "total": 100, "page": 1, "limit": 10, "totalPages": 10 }
}
```
Errors:
```json
{
"statusCode": 400,
"success": false,
"error": { "message": ["..."] }
}
```
### Domain events
Services emit events via `EventEmitter2`. Listeners in `listeners/` handle side effects (notifications, chat, payment follow-up) without bloating the main flow.
### Configuration
Use `ConfigService` for all environment variables. `ConfigModule` is registered globally in `AppModule`. Never read `process.env` directly in application code.
### Rate limiting
`@nestjs/throttler` is configured globally (5 requests per 60 seconds). Sensitive endpoints (OTP, refresh token) use stricter per-route limits via `@Throttle` or custom decorators.
## Design principles
- **Thin controllers, fat services** — HTTP concerns stay in controllers; business rules live in services.
- **Reuse before rewrite** — Search for existing services, DTOs, and repositories before adding new ones.
- **Module boundaries** — Features communicate through exported services or domain events, not by reaching into another module's internals.
- **No duplicated logic** — Extend existing modules rather than copying patterns.
- **Transactions for multi-entity writes** — Use `em.transactional()` when several rows must commit or roll back together.
## External integrations
| Integration | Purpose |
|-------------|---------|
| SMS.ir | OTP and transactional SMS |
| Zarinpal | Payment gateway |
| S3-compatible storage | File uploads |
| Redis | OTP cache, general caching, BullMQ |
## Local development
```bash
npm install
npm run start:dev # Watch mode on APP_PORT (default 4000)
```
Swagger UI: `http://localhost:4000/docs`
Database helpers (see [database.md](./database.md)):
```bash
npm run migration:up # Apply pending migrations
npm run db:seed # Run seeders
npm run db:reset # Drop, create, migrate, seed (destructive)
```
+171
View File
@@ -0,0 +1,171 @@
# Authentication & Authorization
Negareh API supports two actor types — **users** (customers) and **admins** (back-office). Both authenticate via **phone OTP** and receive **JWT access tokens** with **refresh tokens**.
## Overview
```
┌─────────────┐ OTP request ┌─────────────┐
│ Client │ ──────────────────► │ Auth API │
│ │ ◄────────────────── │ (SMS/Redis)│
└─────────────┘ OTP code └─────────────┘
│ │
│ OTP verify │
└──────────────────────────────────► │
┌─────────────────────────┐
│ Access JWT + Refresh │
│ token (hashed in DB) │
└─────────────────────────┘
```
## OTP flow
### User (public)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/public/auth/otp/request` | POST | Send OTP to phone (creates user on verify) |
| `/public/auth/otp/verify` | POST | Verify OTP → returns tokens + user profile |
| `/public/auth/refresh` | POST | Exchange refresh token for new token pair |
### Admin
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/admin/auth/otp/request` | POST | Send OTP (phone must exist in `admins` table) |
| `/admin/auth/otp/verify` | POST | Verify OTP → returns tokens + admin profile |
| `/admin/auth/refresh` | POST | Exchange refresh token for new token pair |
### OTP details
- Phone numbers are normalized to local format via `normalizePhoneToLocal()` (`09xxxxxxxxx`).
- OTP codes are 5-digit numeric values stored in **Redis** with TTL (`OTP_EXPIRATION_TIME`, default 240 seconds).
- OTP request endpoints are rate-limited (`@Throttle`: 3 requests per 180 seconds).
- SMS delivery uses SMS.ir patterns (configured via `SMS_*` env vars).
### Request / verify DTOs
```typescript
// Request OTP
{ "phone": "09362532122" }
// Verify OTP
{ "phone": "09362532122", "otp": "12345" }
```
## JWT tokens
### Access token
- Signed with `JWT_SECRET`.
- Expiration: `JWT_EXPIRATION_TIME` (seconds).
- Sent as `Authorization: Bearer <token>`.
**User payload:**
```typescript
{ userId: string }
```
**Admin payload:**
```typescript
{ adminId: string }
```
### Refresh token
- Opaque 64-character hex string (not a JWT).
- Stored **hashed** (SHA-256) in the `refresh_tokens` table.
- Expiration: `REFRESH_TOKEN_EXPIRE` (days).
- Rotated on each refresh — old token is deleted in the same transaction as the new pair.
- Type field distinguishes `user` vs `admin` tokens.
### Refresh response shape
```json
{
"accessToken": { "token": "...", "expire": 1710000000000 },
"refreshToken": { "token": "...", "expire": 1711000000000 }
}
```
`expire` values are Unix timestamps in milliseconds.
## Guards
### `AuthGuard` (users)
- Applied with `@UseGuards(AuthGuard)` on public routes that require a logged-in user.
- Verifies JWT from `Authorization` header.
- Sets `request.userId` from the token payload.
- Extract identity in controllers with `@UserId()`.
### `AdminAuthGuard` (admins)
- Applied with `@UseGuards(AdminAuthGuard)` on admin routes.
- Verifies JWT and sets `request.adminId`.
- Extract identity with `@AdminId()`.
- Reads `@Permissions(...)` metadata from the handler/class for RBAC checks.
## Role-based access control (RBAC)
Admins have a **role** with attached **permissions**. Permission names are defined in `PermissionEnum` (e.g. `view_orders`, `create_invoice`, `manage_admins`).
### Declaring required permissions
```typescript
@Get('admin/orders')
@UseGuards(AdminAuthGuard)
@Permissions(PermissionEnum.VIEW_ORDERS)
findAllAsAdmin(@Query() dto: FindOrdersDto) {
return this.orderService.findOrdersAsAdmin(dto);
}
```
The `@Permissions()` decorator stores required permission keys in metadata. `AdminAuthGuard` reads them via `Reflector`.
> **Note:** Permission enforcement against the database/cache is partially implemented. The guard structure and decorators are in place; ensure permission checks are active before relying on them in production.
## Route prefixes
Routes are namespaced by audience in the controller path (not a global prefix):
| Prefix | Audience | Guard |
|--------|----------|-------|
| `public/` | End users | `AuthGuard` (when protected) |
| `admin/` | Back-office | `AdminAuthGuard` |
Unauthenticated routes use `public/auth/...` or `admin/auth/...` without guards.
## WebSocket auth
Admin WebSocket connections use `WsAdminAuthGuard` in the notifications module. Follow the same JWT verification pattern as HTTP admin auth.
## Environment variables
| Variable | Purpose |
|----------|---------|
| `JWT_SECRET` | Signing key for access tokens |
| `JWT_EXPIRATION_TIME` | Access token TTL (seconds) |
| `REFRESH_TOKEN_EXPIRE` | Refresh token TTL (days) |
| `OTP_EXPIRATION_TIME` | OTP cache TTL (seconds) |
| `REDIS_URI` / `REDIS_HOST` | OTP and cache storage |
| `SMS_BASE_URL`, `SMS_API_KEY`, `SMS_PATTERN_OTP` | SMS delivery |
## Security practices
- Refresh tokens are hashed at rest; raw tokens are only returned once to the client.
- Token refresh runs inside a database transaction to prevent race conditions.
- Rate limiting on OTP and refresh endpoints reduces brute-force risk.
- Never log tokens, OTP codes, or secrets.
- Use HTTPS in production; CORS is enabled with `credentials: true` for cookie support if needed.
## Client integration checklist
1. Request OTP with normalized Iranian mobile number.
2. Verify OTP and store both `accessToken.token` and `refreshToken.token`.
3. Send `Authorization: Bearer <accessToken>` on protected requests.
4. On 401, call the refresh endpoint with the refresh token.
5. Replace stored tokens with the new pair from the refresh response.
+113
View File
@@ -0,0 +1,113 @@
# Coding Style
Conventions for writing and reviewing code in Negareh API. These align with the project's Cursor rules and existing patterns.
## TypeScript
- **Strict typing** — Never use `any`. Prefer `unknown` when the type is genuinely unknown, then narrow.
- **Exported functions** — Add explicit return types.
- **Interfaces for DTOs** — Use classes with decorators for request bodies; interfaces for internal shapes and transformers.
- **Enums** — Use only when a fixed set of values is shared across layers (e.g. `PermissionEnum`, status enums). Avoid enums for one-off string unions.
- **Readonly** — Prefer `readonly` on constructor-injected dependencies and immutable fields.
- **Function size** — Keep functions under ~40 lines when practical; extract private methods for clarity.
## NestJS patterns
### Dependency injection
- Use **constructor injection** for all dependencies.
- Register providers in the feature module; export only what other modules need.
- Use `forwardRef()` when circular module dependencies are unavoidable (e.g. `AuthModule``UserModule`).
### Controllers
- Keep controllers **thin** — validate input, call service, return result.
- Use `@ApiTags`, `@ApiOperation`, and `@ApiBearerAuth` for Swagger.
- Apply guards at method level: `@UseGuards(AuthGuard)` or `@UseGuards(AdminAuthGuard)`.
- Extract identity from decorators: `@UserId()`, `@AdminId()`.
### Services
- All **business logic** belongs in services (`providers/`).
- Throw NestJS `HttpException` subclasses (`BadRequestException`, `NotFoundException`, etc.).
- Use `ConfigService` instead of `process.env`.
### DTOs
Every request body and query object must have a DTO class:
```typescript
import { IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
export class ExampleDto {
@IsNotEmpty()
@IsString()
@ApiProperty({ example: 'value' })
@Transform(({ value }) => value?.trim())
name: string;
}
```
- Use `class-validator` decorators for validation.
- Use `class-transformer` (`@Transform`) for normalization (e.g. phone numbers).
- Document fields with `@ApiProperty` for Swagger.
## File and naming conventions
| Artifact | Convention | Example |
|----------|------------|---------|
| Module | `<feature>.module.ts` | `order.module.ts` |
| Controller | `<feature>.controller.ts` in `controllers/` | `order.controller.ts` |
| Service | `<feature>.service.ts` in `providers/` | `order.service.ts` |
| Repository | `<entity>.repository.ts` in `repositories/` | `order.repository.ts` |
| Entity | `<entity>.entity.ts` in `entities/` | `order.entity.ts` |
| DTO | `<action>-<entity>.dto.ts` in `dto/` | `create-order.dto.ts` |
| Event | `<Entity><Action>Event` in `events/` | `OrderCreatedEvent` |
| Listener | `<feature>.listeners.ts` in `listeners/` | `order.listeners.ts` |
- Use **kebab-case** for file names.
- Use **PascalCase** for classes, **camelCase** for methods and variables.
- Folder names: `controllers`, `providers`, `repositories`, `entities`, `dto`, `listeners`, `events`, `enum`.
## MikroORM usage
- Prefer **EntityRepository** subclasses over raw `EntityManager` queries in services.
- Use **`populate`** instead of manual joins.
- Use **`wrap(entity).assign(dto)`** for partial updates.
- Use **`em.transactional()`** when updating multiple entities atomically.
- Use **`Collection`** for `OneToMany` relations.
- Avoid **N+1** — batch loads and selective `populate`.
- Avoid **raw SQL** unless necessary (triggers, complex reports). Migrations are the exception.
- Type repository injections explicitly.
## Performance
- Minimize database round-trips; batch where possible.
- Paginate large lists — return `PaginatedResult<T>` with `data` and `meta`.
- Select only required columns; avoid loading full entity graphs when a subset suffices.
- Index foreign keys and frequently filtered columns.
## Error handling
- Use specific HTTP exceptions with clear messages.
- Shared user-facing messages live in `src/common/enums/message.enum.ts`.
- Do not swallow errors silently; log unexpected failures with NestJS `Logger`.
## Formatting and linting
```bash
npm run format # Prettier
npm run lint # ESLint (with fix)
npm run build # TypeScript compile check
```
Match the style of surrounding code. Do not reformat unrelated files in the same change.
## Before adding new code
1. Search for an existing implementation in `src/modules/` and `src/common/`.
2. Reuse existing services, DTOs, and repositories.
3. Follow the module's existing folder layout.
4. Extend rather than duplicate.
+145
View File
@@ -0,0 +1,145 @@
# Database
Negareh API uses **PostgreSQL** with **MikroORM 6**. Schema changes are managed through migrations — never rely on `synchronize` in production.
## Connection
Configuration lives in `src/config/mikro-orm.config.ts` and reads from environment variables:
| Variable | Description |
|----------|-------------|
| `DB_HOST` | PostgreSQL host |
| `DB_PORT` | PostgreSQL port |
| `DB_USER` | Database user |
| `DB_PASS` | Database password |
| `DB_NAME` | Database name |
| `NODE_ENV` | `production` disables auto schema ensure |
In non-production, MikroORM can auto-create/update the database schema on startup (`ensureDatabase`). In production, only migrations should change the schema.
## Entity conventions
All persistent models extend `BaseEntity`:
```typescript
// src/common/entities/base.entity.ts
@Filter({ name: 'notDeleted', cond: { deletedAt: null }, default: true })
export abstract class BaseEntity {
@PrimaryKey({ type: 'string', columnType: 'char(26)' })
id: string = ulid();
@Property({ defaultRaw: 'now()', columnType: 'timestamptz' })
createdAt: Date = new Date();
@Property({ nullable: true, columnType: 'timestamptz' })
deletedAt?: Date;
}
```
### Rules
- **Primary keys** — ULID strings (`char(26)`), not auto-increment integers.
- **Timestamps** — `timestamptz` with UTC (`forceUtcTimezone: true` in config).
- **Soft deletes** — Set `deletedAt` instead of hard-deleting rows. The `notDeleted` filter excludes soft-deleted records by default.
- **Indexes** — Add `@Index` on foreign keys and columns used in `WHERE` / `ORDER BY` clauses.
- **Table names** — Explicit `tableName` in `@Entity({ tableName: 'users' })` when the name differs from the class.
### Relations
- Use MikroORM decorators: `@ManyToOne`, `@OneToMany`, `@ManyToMany`.
- Initialize `OneToMany` with `new Collection<T>(this)`.
- Use `populate` in queries to load relations and avoid N+1.
### Updates
```typescript
wrap(entity).assign(partialDto);
await em.flush();
```
## Repositories
Custom data access extends `EntityRepository<Entity>`:
```typescript
@Injectable()
export class UserRepository extends EntityRepository<User> {
constructor(readonly em: EntityManager) {
super(em, User);
}
async findAllPaginated(dto: FindUsersDto): Promise<PaginatedResult<User>> {
// build FilterQuery, findAndCount with limit/offset
}
}
```
Register repositories as providers in the feature module alongside `MikroOrmModule.forFeature([Entity])`.
## Migrations
Migrations live in `database/migrations/` and are emitted as TypeScript.
### Commands
| Script | Action |
|--------|--------|
| `npm run migration:create` | Generate migration from entity diff |
| `npm run migration:blank` | Create empty migration file |
| `npm run migration:up` | Apply pending migrations |
| `npm run migration:down` | Revert last migration |
| `npm run migration:list` | List all migrations |
| `npm run migration:pending` | Show unapplied migrations |
| `npm run migration:fresh` | Drop and re-run all migrations |
### Guidelines
- **Always use migrations** for schema changes in shared environments.
- Migrations are **transactional** (`allOrNothing: true`).
- `safe: true` prevents destructive drops in generated migrations — review each file before applying.
- Complex logic (triggers, functions) belongs in migrations — see `Migration20260626140000` for invoice total recalculation triggers.
- **Never remove data** in migrations unless explicitly requested.
### Workflow
1. Modify entity files.
2. Run `npm run migration:create`.
3. Review the generated SQL in `database/migrations/`.
4. Run `npm run migration:up`.
5. Commit the migration file with the entity changes.
## Seeders
Seeders populate development/staging data. Entry point: `src/seeders/DatabaseSeeder.ts`.
```bash
npm run db:seed # Run all seeders
npm run db:reset # Drop + create + migrate + seed (destructive)
```
Seeder structure:
```
src/seeders/
├── DatabaseSeeder.ts # Orchestrates seed order
├── <entity>.seeder.ts # Per-entity seed logic
└── data/ # Static seed data arrays
```
Seed order matters — permissions and roles must exist before admins; categories before products.
## Query guidelines
- Avoid `SELECT *` in raw queries; MikroORM entity loads are fine but use `fields` option when you only need a subset.
- Design filters to hit indexes (`$ilike` on indexed columns sparingly; prefer exact match on indexed fields).
- Use `findAndCount` for paginated lists.
- Wrap multi-step writes in `em.transactional(async (em) => { ... })`.
## Connection pool
Pool settings are tuned per environment in `mikro-orm.config.ts`:
- Production: min 5, max 20 connections
- Development: min 2, max 10 connections
Statement and idle timeouts are set to 60 seconds to prevent hung transactions.