add docs
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user