# Resident API Documentation

Dành cho Frontend Team - Tài liệu API Resident và File Storage

---

## Table of Contents

- [Overview](#overview)
- [Authentication](#authentication)
- [Response Format](#response-format)
- [Resident APIs](#resident-apis)
- [Resident Document APIs](#resident-document-apis)
- [File Storage APIs](#file-storage-apis)
- [Building APIs](#building-apis)
- [Apartment APIs](#apartment-apis)
- [Room APIs](#room-apis)
- [Bed APIs](#bed-apis)
- [Complete Workflows](#complete-workflows)
- [Error Handling](#error-handling)

---

## Overview

Resident API quản lý thông tin cư dân, bao gồm:

- Thông tin cá nhân (họ tên, email, phone, CCCD...)
- Thông tin chỗ ở (building, apartment, room, bed)
- Tài liệu định danh (CCCD, hộ khẩu, hợp đồng...)
- Liên kết với User account

**Base URL**: `https://api.example.com/api/v1`

---

## Authentication

Tất cả API đều yêu cầu Bearer token trong header:

```http
Authorization: Bearer <access_token>
```

**Roles**:

- `admin`: Quản trị viên - full access
- `system_admin`: Quản trị viên hệ thống - full access
- User thường: Chỉ xem thông tin của chính mình

---

## Response Format

### Success Response

```json
{
  "success": true,
  "trace_id": null,
  "timestamp": "2024-01-15T08:30:00.000Z",
  "errors": null,
  "data": { ... }
}
```

### Error Response

```json
{
	"error": {
		"code": "VALIDATION_ERROR",
		"message": "Validation failed",
		"details": [
			{
				"field": "email",
				"code": "INVALID_EMAIL",
				"messages": {
					"vi": "Email không hợp lệ",
					"en": "Invalid email format"
				}
			}
		]
	}
}
```

---

## Resident APIs

### 1. Get Residents List

Lấy danh sách cư dân với thông tin chỗ ở

```http
GET /api/v1/residents
```

**Middleware**: `verify`, `requireAdmin`, `validateQueryZod(QueryParamsSchema)`, `queryModifier`

**Query Parameters**:

| Parameter | Type   | Default | Max | Description                                         |
| --------- | ------ | ------- | --- | --------------------------------------------------- |
| filters   | string | -       | -   | Filter theo cột (xem sequelize-api-paginate syntax) |
| sortField | string | -       | -   | Trường sắp xếp                                      |
| sortOrder | enum   | -       | -   | `asc` hoặc `desc`                                   |
| page      | number | 1       | -   | Số trang                                            |
| pageSize  | number | 10      | 100 | Số item mỗi trang                                   |

**Example**:

```http
GET /api/v1/residents?page=1&pageSize=20&sortField=created_at&sortOrder=desc&filters[status]=active
```

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"count": 100,
		"rows": [
			{
				"id": "550e8400-e29b-41d4-a716-446655440001",
				"user_id": "550e8400-e29b-41d4-a716-446655440000",
				"user": {
					"id": "550e8400-e29b-41d4-a716-446655440000",
					"email": "nguyenvana@example.com",
					"username": "nguyenvana",
					"first_name": "Nguyễn",
					"last_name": "Văn A",
					"phone": "+84 901 234 567",
					"status": "active"
				},
				"full_name": "Nguyễn Văn A",
				"email": "nguyenvana@example.com",
				"phone": "+84 901 234 567",
				"id_card": "001234567890",
				"date_of_birth": "1990-05-15",
				"emergency_contact_name": "Nguyễn Thị B",
				"emergency_contact_phone": "+84 902 345 678",
				"emergency_contact_relationship": "Vợ",
				"status": "active",
				"version": 1,
				"created_at": "2024-01-15T08:30:00.000Z",
				"updated_at": "2024-01-20T10:00:00.000Z",
				"deleted_at": null,
				"residence": {
					"building_id": "880e8400-e29b-41d4-a716-446655440001",
					"building_name": "Tòa nhà A",
					"building_code": "TNA-001",
					"apartment_id": "990e8400-e29b-41d4-a716-446655440001",
					"apartment_name": "Căn hộ 101",
					"apartment_code": "CH-101",
					"apartment_floor": 1,
					"room_id": "aa0e8400-e29b-41d4-a716-446655440001",
					"room_name": "Phòng 101A",
					"room_code": "P-101A",
					"bed_id": "bb0e8400-e29b-41d4-a716-446655440001",
					"bed_name": "Giường 1",
					"bed_code": "G-001",
					"move_in_at": "2024-01-15T08:00:00.000Z"
				}
			}
		],
		"page": 1,
		"pageSize": 20
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ hoặc hết hạn
- `FORBIDDEN`: User không có quyền admin

---

### 2. Create Resident

Tạo cư dân mới, có thể kèm thông tin chỗ ở ngay lập tức

```http
POST /api/v1/residents
```

**Middleware**: `verify`, `requireAdmin`, `validateZod(ResidentCreateBodySchema)`

**Request Body**:

```json
{
	"user_id": "550e8400-e29b-41d4-a716-446655440000",
	"full_name": "Nguyễn Văn A",
	"email": "nguyenvana@example.com",
	"phone": "+84 901 234 567",
	"id_card": "001234567890",
	"date_of_birth": "1990-05-15",
	"emergency_contact_name": "Nguyễn Thị B",
	"emergency_contact_phone": "+84 902 345 678",
	"emergency_contact_relationship": "Vợ",
	"status": "active",
	"bed_id": "00000001-0001-0001-0001-000000000014",
	"move_in_at": "2024-01-15T08:30:00.000Z"
}
```

**Field Descriptions**:

| Field                          | Type   | Required | Validation            | Description                   |
| ------------------------------ | ------ | -------- | --------------------- | ----------------------------- |
| user_id                        | UUID   | Optional | Valid UUID            | ID của user account (nếu có)  |
| full_name                      | string | Yes      | min 1, max 200        | Họ tên                        |
| email                          | string | Optional | email format, max 255 | Email                         |
| phone                          | string | Optional | max 25                | Số điện thoại                 |
| id_card                        | string | Optional | max 50                | Số CCCD                       |
| date_of_birth                  | string | Optional | -                     | Ngày sinh (YYYY-MM-DD)        |
| emergency_contact_name         | string | Optional | max 200               | Tên người liên hệ khẩn cấp    |
| emergency_contact_phone        | string | Optional | max 25                | SĐT người liên hệ khẩn cấp    |
| emergency_contact_relationship | string | Optional | max 100               | Mối quan hệ với người liên hệ |
| status                         | enum   | Optional | `active`, `inactive`  | Default: `active`             |
| bed_id                         | UUID   | Optional | Valid UUID            | ID giường để check-in ngay    |
| move_in_at                     | string | Optional | ISO 8601              | Thời gian check-in            |

**Response**:

```json
{
  "success": true,
  "trace_id": null,
  "timestamp": "2024-01-15T08:30:00.000Z",
  "errors": null,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440001",
    "full_name": "Nguyễn Văn A",
    "status": "active",
    "version": 1,
    "created_at": "2024-01-15T08:30:00.000Z",
    ...
  }
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`:
  - `full_name` required, min 1, max 200
  - `email` invalid format nếu có
  - `phone` max 25 nếu có
  - `id_card` max 50 nếu có
  - `emergency_contact_name` max 200 nếu có
  - `emergency_contact_phone` max 25 nếu có
  - `emergency_contact_relationship` max 100 nếu có
- `CONFLICT`: Email, phone, hoặc CCCD đã tồn tại (unique constraint)
- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- Email, phone, CCCD phải unique (database unique constraint)
- Nếu có `bed_id` và `move_in_at`, hệ thống tự tạo `resident_stays` record trong transaction
- `created_by` và `updated_by` được tự động set từ `req.user.id`

---

### 3. Get Resident Detail

Lấy chi tiết cư dân kèm thông tin chỗ ở và tài liệu

```http
GET /api/v1/residents/{id}
```

**Middleware**: `verify`, `validateId`

**Path Parameters**:

| Parameter | Type | Required | Description     |
| --------- | ---- | -------- | --------------- |
| id        | UUID | Yes      | ID của resident |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "550e8400-e29b-41d4-a716-446655440001",
		"user_id": "550e8400-e29b-41d4-a716-446655440000",
		"user": {
			"id": "550e8400-e29b-41d4-a716-446655440000",
			"email": "nguyenvana@example.com",
			"username": "nguyenvana",
			"first_name": "Nguyễn",
			"last_name": "Văn A",
			"phone": "+84 901 234 567",
			"status": "active"
		},
		"full_name": "Nguyễn Văn A",
		"email": "nguyenvana@example.com",
		"phone": "+84 901 234 567",
		"id_card": "001234567890",
		"date_of_birth": "1990-05-15",
		"emergency_contact_name": "Nguyễn Thị B",
		"emergency_contact_phone": "+84 902 345 678",
		"emergency_contact_relationship": "Vợ",
		"status": "active",
		"version": 1,
		"created_at": "2024-01-15T08:30:00.000Z",
		"updated_at": "2024-01-20T10:00:00.000Z",
		"deleted_at": null,
		"residence": {
			"building_id": "880e8400-e29b-41d4-a716-446655440001",
			"building_name": "Tòa nhà A",
			"building_code": "TNA-001",
			"apartment_id": "990e8400-e29b-41d4-a716-446655440001",
			"apartment_name": "Căn hộ 101",
			"apartment_code": "CH-101",
			"apartment_floor": 1,
			"room_id": "aa0e8400-e29b-41d4-a716-446655440001",
			"room_name": "Phòng 101A",
			"room_code": "P-101A",
			"bed_id": "bb0e8400-e29b-41d4-a716-446655440001",
			"bed_name": "Giường 1",
			"bed_code": "G-001",
			"move_in_at": "2024-01-15T08:00:00.000Z"
		},
		"documents": [
			{
				"id": "660e8400-e29b-41d4-a716-446655440001",
				"resident_id": "550e8400-e29b-41d4-a716-446655440001",
				"file_id": "770e8400-e29b-41d4-a716-446655440001",
				"document_type": "id_card",
				"document_name": "CCCD Nguyễn Văn A",
				"description": "Căn cước công dân",
				"is_primary": true,
				"issued_date": "2020-01-15",
				"expiry_date": "2030-01-15",
				"verified_at": "2024-01-16T09:00:00.000Z",
				"verified_by": "550e8400-e29b-41d4-a716-446655440002",
				"created_at": "2024-01-15T08:30:00.000Z",
				"updated_at": "2024-01-16T09:00:00.000Z",
				"deleted_at": null
			}
		]
	}
}
```

**Permissions**:

- `admin`, `system_admin`: Xem tất cả
- User thường: Chỉ xem thông tin của chính mình (`req.user.id === resident.user_id`)

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền xem resident này
- `NOT_FOUND`: Resident không tồn tại hoặc đã bị soft delete

**Note**: `residence` có thể là `null` nếu resident chưa có chỗ ở. `documents` có thể là mảng rỗng.

---

### 4. Update Resident

Cập nhật thông tin cư dân

```http
PUT /api/v1/residents/{id}
```

**Middleware**: `verify`, `queryModifier`, `validateId`, `validateZod(ResidentUpdateBodySchema)`

**Request Body**:

```json
{
	"user_id": "550e8400-e29b-41d4-a716-446655440000",
	"full_name": "Nguyễn Văn A Updated",
	"email": "newemail@example.com",
	"phone": "+84 903 456 789",
	"id_card": "001234567890",
	"date_of_birth": "1990-05-15",
	"version": 1,
	"emergency_contact_name": "Nguyễn Thị C",
	"emergency_contact_phone": "+84 904 567 890",
	"emergency_contact_relationship": "Chị gái",
	"status": "active"
}
```

**Important**:

- `version` field required cho optimistic locking
- Tất cả fields đều optional
- `updated_at` được tự động set
- `updated_by` được tự động set từ `req.user.id`

**Response**: Trả về resident đã cập nhật với residence info (không có documents)

**Permissions**:

- `admin`, `system_admin`: Cập nhật tất cả
- User thường: Chỉ cập nhật thông tin của chính mình (`req.user.id === resident.user_id`)

**Errors**:

- `VALIDATION_ERROR`: Tương tự create
- `CONFLICT`: Email, phone, hoặc CCCD conflict với resident khác
- `NOT_FOUND`: Resident không tồn tại
- `FORBIDDEN`: User không có quyền cập nhật
- `VERSION_CONFLICT`: Version không khớp (optimistic locking failure)

---

### 5. Delete Resident

Xóa cư dân (soft delete)

```http
DELETE /api/v1/residents/{id}
```

**Middleware**: `verify`, `requireAdmin`, `validateId`

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": "Successfully deleted item"
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin
- `NOT_FOUND`: Resident không tồn tại

**Notes**:

- Soft delete: set `deleted_at`, `deleted_by`
- Data vẫn còn trong DB, chỉ ẩn khỏi list
- Có thể restore (chưa implement trong API hiện tại)
- `deleted_by` được set từ `req.user.id`

---

## Resident Document APIs

### 1. Get Resident Documents

Lấy danh sách tài liệu của cư dân

```http
GET /api/v1/residents/{id}/documents
```

**Middleware**: `verify`, `validateId`

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"count": 2,
		"rows": [
			{
				"id": "660e8400-e29b-41d4-a716-446655440001",
				"resident_id": "550e8400-e29b-41d4-a716-446655440001",
				"file_id": "770e8400-e29b-41d4-a716-446655440001",
				"document_type": "id_card",
				"document_name": "CCCD Nguyễn Văn A",
				"description": "Căn cước công dân",
				"is_primary": true,
				"issued_date": "2020-01-15",
				"expiry_date": "2030-01-15",
				"verified_at": "2024-01-16T09:00:00.000Z",
				"verified_by": "550e8400-e29b-41d4-a716-446655440002",
				"created_at": "2024-01-15T08:30:00.000Z",
				"updated_at": "2024-01-16T09:00:00.000Z",
				"deleted_at": null
			}
		]
	}
}
```

**Permissions**:

- `admin`, `system_admin`: Xem tất cả
- User thường: Chỉ xem tài liệu của chính mình (`req.user.id === resident.user_id`)

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền xem
- `NOT_FOUND`: Resident không tồn tại

**Important**: Response chỉ có `file_id`, KHÔNG có file URL. Phải gọi File API để lấy URL.

---

### 2. Create Resident Document

Gắn tài liệu vào cư dân

```http
POST /api/v1/residents/{id}/documents
```

**Middleware**: `verify`, `requireAdmin`, `validateId`,
`validateZod(ResidentDocumentCreateBodySchema)`

**Request Body**:

```json
{
	"file_id": "770e8400-e29b-41d4-a716-446655440001",
	"document_type": "id_card",
	"document_name": "CCCD Nguyễn Văn A",
	"description": "Căn cước công dân",
	"is_primary": true
}
```

**Document Types**:

| Type                     | Description       |
| ------------------------ | ----------------- |
| `id_card`                | Căn cước công dân |
| `passport`               | Hộ chiếu          |
| `household_registration` | Sổ hộ khẩu        |
| `temporary_residence`    | Tạm trú           |
| `contract`               | Hợp đồng          |
| `other`                  | Khác              |

**Field Descriptions**:

| Field         | Type    | Required | Validation     | Description                               |
| ------------- | ------- | -------- | -------------- | ----------------------------------------- |
| file_id       | UUID    | Yes      | Valid UUID     | ID của file đã upload qua `/api/v1/files` |
| document_type | enum    | Yes      | -              | Loại tài liệu                             |
| document_name | string  | Optional | max 255        | Tên tài liệu                              |
| description   | string  | Optional | -              | Mô tả                                     |
| is_primary    | boolean | Optional | default: false | Đánh dấu tài liệu chính                   |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "660e8400-e29b-41d4-a716-446655440001",
		"resident_id": "550e8400-e29b-41d4-a716-446655440001",
		"file_id": "770e8400-e29b-41d4-a716-446655440001",
		"document_type": "id_card",
		"document_name": "CCCD Nguyễn Văn A",
		"description": "Căn cước công dân",
		"is_primary": true,
		"created_at": "2024-01-15T08:30:00.000Z"
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`:
  - `file_id` required, valid UUID
  - `document_type` required
  - `document_name` max 255 nếu có
- `NOT_FOUND`: Resident không tồn tại
- `CONFLICT`:
  - `file_id` đã được gán cho resident này (unique constraint)
  - Đã có tài liệu chính cho `document_type` này (unique constraint)
- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

**Constraints**:

- Mỗi resident chỉ có 1 tài liệu chính cho mỗi `document_type` (unique constraint)
- `file_id` phải unique cho resident (không thể gán cùng file cho nhiều resident)
- `created_by` được tự động set từ `req.user.id`

---

## File Storage APIs

### 1. Upload File

Upload file (qua backend)

```http
POST /api/v1/files
Content-Type: multipart/form-data
```

**Middleware**: `verify`, `queryModifier`, `uploadRateLimit`, `upload`,
`validateZod(FileUploadBodyValidationSchema)`

**Form Data**:

| Field       | Type    | Required | Validation | Description                        |
| ----------- | ------- | -------- | ---------- | ---------------------------------- |
| files       | File    | Yes      | -          | File cần upload                    |
| type        | string  | Optional | -          | Loại file (image, document, video) |
| title       | string  | Optional | max 150    | Tiêu đề file                       |
| description | string  | Optional | max 500    | Mô tả                              |
| note        | string  | Optional | max 255    | Ghi chú                            |
| is_library  | boolean | Optional | -          | Lưu vào thư viện (default: false)  |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "770e8400-e29b-41d4-a716-446655440001",
		"name": "avatar-abc123.webp",
		"original_name": "my-photo.jpg",
		"mime": "image/webp",
		"size": 48200,
		"storage_provider": "minio",
		"owner_user_id": "550e8400-e29b-41d4-a716-446655440000",
		"title": "Avatar của tôi",
		"description": "Avatar profile",
		"category": "image",
		"status": "uploaded",
		"created_at": "2024-01-15T08:30:00.000Z"
	}
}
```

**Permissions**: Tất cả user đã authenticate

**Errors**:

- `VALIDATION_ERROR`:
  - `files` required (MISSING_FILE_FIELDS)
  - `title` max 150 nếu có
  - `description` max 500 nếu có
  - `note` max 255 nếu có
- `FILE_UPLOAD_FAILED`: Upload thất bại
- `RATE_LIMIT_EXCEEDED`: Quá rate limit
- `UNAUTHORIZED`: Token không hợp lệ

**Notes**:

- File được tự động compress nếu là image
- Variants (thumbnail, etc.) được tạo tự động
- `owner_user_id` được set từ `req.user.id`
- Transaction rollback nếu upload fail

---

### 2. Get File List

Lấy danh sách file với thumbnail_url

```http
GET /api/v1/files
```

**Middleware**: `verify`, `validateQueryZod(QueryParamsSchema)`, `queryModifier`

**Query Parameters**: Tương tự resident list

**Special Behavior**:

- Max pageSize enforced = 50 (để bound concurrent MinIO calls)
- `thumbnail_url` được inject vào mỗi file item
- Admin xem tất cả files, user thường chỉ xem files của mình (`owner_user_id`)

**Response**:

```json
{
  "success": true,
  "trace_id": null,
  "timestamp": "2024-01-15T08:30:00.000Z",
  "errors": null,
  "data": {
    "count": 10,
    "rows": [
      {
        "id": "770e8400-e29b-41d4-a716-446655440001",
        "name": "avatar.webp",
        "thumbnail_url": "https://minio.example.com/bucket/thumbnail.webp?signature=...",
        ...
      }
    ],
    "page": 1,
    "pageSize": 20
  }
}
```

**Permissions**: Tất cả user đã authenticate

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ

---

### 3. Get File URL (Single)

Lấy URL để xem/tải file

```http
GET /api/v1/files/{id}/link?mode=view
```

**Middleware**: `verify`, `validateId`

**Query Parameters**:

| Parameter | Type   | Default | Max   | Description                           |
| --------- | ------ | ------- | ----- | ------------------------------------- |
| mode      | string | view    | -     | `view` (preview), `download` (tải về) |
| expires   | number | 3600    | 86400 | TTL của signed URL (giây)             |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"url": "https://minio.example.com/bucket/file.webp?signature=...&expires=3600",
		"fallback_endpoint": null,
		"expires_in": 3600
	}
}
```

**Response cho Local Storage**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"url": null,
		"fallback_endpoint": "https://api.example.com/api/v1/files/770e8400-e29b-41d4-a716-446655440001/content?mode=view",
		"expires_in": null
	}
}
```

**Permissions**:

- `admin`, `system_admin`: Truy cập tất cả file
- User thường: Chỉ file của mình (`file.owner_user_id === req.user.id`)

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền truy cập file
- `NOT_FOUND`: File không tồn tại

---

### 4. Get File URLs (Batch)

Lấy URL cho nhiều file cùng lúc

```http
POST /api/v1/files/batch-link
```

**Middleware**: `verify`

**Request Body**:

```json
{
	"file_ids": [
		"770e8400-e29b-41d4-a716-446655440001",
		"770e8400-e29b-41d4-a716-446655440002",
		"770e8400-e29b-41d4-a716-446655440003"
	],
	"mode": "view"
}
```

**Parameters**:

| Field    | Type   | Required | Validation | Description                  |
| -------- | ------ | -------- | ---------- | ---------------------------- |
| file_ids | array  | Yes      | max 100    | Danh sách file IDs           |
| mode     | string | Optional | -          | `view` (default), `download` |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": [
		{
			"file_id": "770e8400-e29b-41d4-a716-446655440001",
			"url": "https://minio.example.com/bucket/file1.webp?signature=...",
			"fallback_endpoint": null,
			"expires_in": 3600
		},
		{
			"file_id": "770e8400-e29b-41d4-a716-446655440002",
			"url": null,
			"fallback_endpoint": "https://api.example.com/api/v1/files/770e8400-e29b-41d4-a716-446655440002/content?mode=view",
			"expires_in": null
		},
		{
			"file_id": "770e8400-e29b-41d4-a716-446655440003",
			"url": null,
			"fallback_endpoint": null,
			"expires_in": null,
			"error": "File not found"
		}
	]
}
```

**Permissions**: Tương tự single link

**Errors**:

- `VALIDATION_ERROR`:
  - `file_ids` required, phải là mảng
  - `file_ids` tối đa 100 items
- `UNAUTHORIZED`: Token không hợp lệ

**Benefits**:

- 1 request thay vì N requests
- Tối ưu cho gallery/list với nhiều file
- File lỗi không làm hỏng cả batch (trả về `error` field)

---

## Building APIs

### 1. Get Buildings List

Lấy danh sách tòa nhà

```http
GET /api/v1/buildings
```

**Middleware**: `verify`, `requireAdmin`, `validateQueryZod(QueryParamsSchema)`, `queryModifier`

**Query Parameters**: Tương tự resident list

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"count": 10,
		"rows": [
			{
				"id": "880e8400-e29b-41d4-a716-446655440001",
				"name": "Tòa nhà A",
				"code": "TNA-001",
				"address": "123 Đường ABC, Quận 1, TP. Hồ Chí Minh",
				"description": "Tòa nhà cao cấp 20 tầng",
				"status": "active",
				"created_at": "2024-01-15T08:30:00.000Z",
				"updated_at": "2024-01-20T10:00:00.000Z",
				"deleted_at": null
			}
		],
		"page": 1,
		"pageSize": 10
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

---

### 2. Create Building

Tạo tòa nhà mới

```http
POST /api/v1/buildings
```

**Middleware**: `verify`, `requireAdmin`, `validateZod(BuildingCreateBodySchema)`

**Request Body**:

```json
{
	"name": "Tòa nhà A",
	"code": "TNA-001",
	"address": "123 Đường ABC, Quận 1, TP. Hồ Chí Minh",
	"description": "Tòa nhà cao cấp 20 tầng",
	"status": "active"
}
```

**Field Descriptions**:

| Field       | Type   | Required | Validation           | Description         |
| ----------- | ------ | -------- | -------------------- | ------------------- |
| name        | string | Yes      | min 1, max 200       | Tên tòa nhà         |
| code        | string | Yes      | min 1, max 50        | Mã tòa nhà (unique) |
| address     | string | Optional | -                    | Địa chỉ             |
| description | string | Optional | -                    | Mô tả               |
| status      | enum   | Optional | `active`, `inactive` | Default: `active`   |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "880e8400-e29b-41d4-a716-446655440001",
		"name": "Tòa nhà A",
		"code": "TNA-001",
		"address": "123 Đường ABC, Quận 1, TP. Hồ Chí Minh",
		"description": "Tòa nhà cao cấp 20 tầng",
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z"
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`:
  - `name` required, min 1, max 200
  - `code` required, min 1, max 50
- `CONFLICT`: Code đã tồn tại (unique constraint)
- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `code` phải unique
- `created_by` được tự động set từ `req.user.id`

---

### 3. Get Building Detail

Lấy chi tiết tòa nhà

```http
GET /api/v1/buildings/{id}
```

**Middleware**: `verify`, `validateId`

**Path Parameters**:

| Parameter | Type | Required | Description    |
| --------- | ---- | -------- | -------------- |
| id        | UUID | Yes      | ID của tòa nhà |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "880e8400-e29b-41d4-a716-446655440001",
		"name": "Tòa nhà A",
		"code": "TNA-001",
		"address": "123 Đường ABC, Quận 1, TP. Hồ Chí Minh",
		"description": "Tòa nhà cao cấp 20 tầng",
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z",
		"updated_at": "2024-01-20T10:00:00.000Z",
		"deleted_at": null
	}
}
```

**Permissions**: Tất cả user đã authenticate

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `NOT_FOUND`: Building không tồn tại

---

### 4. Update Building

Cập nhật thông tin tòa nhà

```http
PUT /api/v1/buildings/{id}
```

**Middleware**: `verify`, `requireAdmin`, `queryModifier`, `validateId`,
`validateZod(BuildingUpdateBodySchema)`

**Request Body**:

```json
{
	"name": "Tòa nhà A Updated",
	"code": "TNA-001",
	"address": "456 Đường XYZ, Quận 2, TP. Hồ Chí Minh",
	"description": "Tòa nhà cao cấp 25 tầng",
	"status": "active"
}
```

**Field Descriptions**: Tất cả fields optional, validation tương tự create

**Response**: Trả về building đã cập nhật

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`: Tương tự create
- `CONFLICT`: Code đã tồn tại (unique constraint)
- `NOT_FOUND`: Building không tồn tại
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `updated_at` được tự động set
- `updated_by` được tự động set từ `req.user.id`

---

### 5. Delete Building

Xóa tòa nhà (soft delete)

```http
DELETE /api/v1/buildings/{id}
```

**Middleware**: `verify`, `requireAdmin`, `validateId`

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": "Successfully deleted item"
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin
- `NOT_FOUND`: Building không tồn tại

**Notes**:

- Soft delete: set `deleted_at`, `deleted_by`
- `deleted_by` được set từ `req.user.id`

---

## Apartment APIs

### 1. Get Apartments List

Lấy danh sách căn hộ

```http
GET /api/v1/apartments
```

**Middleware**: `verify`, `requireAdmin`, `validateQueryZod(QueryParamsSchema)`, `queryModifier`

**Query Parameters**: Tương tự resident list

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"count": 10,
		"rows": [
			{
				"id": "990e8400-e29b-41d4-a716-446655440001",
				"building_id": "880e8400-e29b-41d4-a716-446655440001",
				"name": "Căn hộ 101",
				"code": "CH-101",
				"floor": 1,
				"status": "active",
				"created_at": "2024-01-15T08:30:00.000Z",
				"updated_at": "2024-01-20T10:00:00.000Z",
				"deleted_at": null
			}
		],
		"page": 1,
		"pageSize": 10
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

---

### 2. Create Apartment

Tạo căn hộ mới

```http
POST /api/v1/apartments
```

**Middleware**: `verify`, `requireAdmin`, `validateZod(ApartmentCreateBodySchema)`

**Request Body**:

```json
{
	"building_id": "880e8400-e29b-41d4-a716-446655440001",
	"name": "Căn hộ 101",
	"code": "CH-101",
	"floor": 1,
	"status": "active"
}
```

**Field Descriptions**:

| Field       | Type   | Required | Validation           | Description                     |
| ----------- | ------ | -------- | -------------------- | ------------------------------- |
| building_id | UUID   | Yes      | Valid UUID           | ID của tòa nhà                  |
| name        | string | Yes      | min 1, max 200       | Tên căn hộ                      |
| code        | string | Yes      | min 1, max 50        | Mã căn hộ (unique per building) |
| floor       | number | Optional | int                  | Tầng                            |
| status      | enum   | Optional | `active`, `inactive` | Default: `active`               |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "990e8400-e29b-41d4-a716-446655440001",
		"building_id": "880e8400-e29b-41d4-a716-446655440001",
		"name": "Căn hộ 101",
		"code": "CH-101",
		"floor": 1,
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z"
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`:
  - `building_id` required, valid UUID
  - `name` required, min 1, max 200
  - `code` required, min 1, max 50
  - `floor` invalid integer nếu có
- `CONFLICT`: Code đã tồn tại trong building (unique constraint)
- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `code` phải unique trong cùng `building_id`
- `created_by` được tự động set từ `req.user.id`

---

### 3. Get Apartment Detail

Lấy chi tiết căn hộ

```http
GET /api/v1/apartments/{id}
```

**Middleware**: `verify`, `validateId`

**Path Parameters**:

| Parameter | Type | Required | Description   |
| --------- | ---- | -------- | ------------- |
| id        | UUID | Yes      | ID của căn hộ |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "990e8400-e29b-41d4-a716-446655440001",
		"building_id": "880e8400-e29b-41d4-a716-446655440001",
		"name": "Căn hộ 101",
		"code": "CH-101",
		"floor": 1,
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z",
		"updated_at": "2024-01-20T10:00:00.000Z",
		"deleted_at": null
	}
}
```

**Permissions**: Tất cả user đã authenticate

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `NOT_FOUND`: Apartment không tồn tại

---

### 4. Update Apartment

Cập nhật thông tin căn hộ

```http
PUT /api/v1/apartments/{id}
```

**Middleware**: `verify`, `requireAdmin`, `queryModifier`, `validateId`,
`validateZod(ApartmentUpdateBodySchema)`

**Request Body**:

```json
{
	"building_id": "880e8400-e29b-41d4-a716-446655440001",
	"name": "Căn hộ 101 Updated",
	"code": "CH-101",
	"floor": 2,
	"status": "active"
}
```

**Field Descriptions**: Tất cả fields optional, validation tương tự create

**Response**: Trả về apartment đã cập nhật

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`: Tương tự create
- `CONFLICT`: Code đã tồn tại trong building (unique constraint)
- `NOT_FOUND`: Apartment không tồn tại
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `updated_at` được tự động set
- `updated_by` được tự động set từ `req.user.id`

---

### 5. Delete Apartment

Xóa căn hộ (soft delete)

```http
DELETE /api/v1/apartments/{id}
```

**Middleware**: `verify`, `requireAdmin`, `validateId`

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": "Successfully deleted item"
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin
- `NOT_FOUND`: Apartment không tồn tại

**Notes**:

- Soft delete: set `deleted_at`, `deleted_by`
- `deleted_by` được set từ `req.user.id`

---

## Room APIs

### 1. Get Rooms List

Lấy danh sách phòng

```http
GET /api/v1/rooms
```

**Middleware**: `verify`, `requireAdmin`, `validateQueryZod(QueryParamsSchema)`, `queryModifier`

**Query Parameters**: Tương tự resident list

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"count": 10,
		"rows": [
			{
				"id": "aa0e8400-e29b-41d4-a716-446655440001",
				"apartment_id": "990e8400-e29b-41d4-a716-446655440001",
				"name": "Phòng 101A",
				"code": "P-101A",
				"floor": 1,
				"status": "active",
				"created_at": "2024-01-15T08:30:00.000Z",
				"updated_at": "2024-01-20T10:00:00.000Z",
				"deleted_at": null
			}
		],
		"page": 1,
		"pageSize": 10
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

---

### 2. Create Room

Tạo phòng mới

```http
POST /api/v1/rooms
```

**Middleware**: `verify`, `requireAdmin`, `validateZod(RoomCreateBodySchema)`

**Request Body**:

```json
{
	"apartment_id": "990e8400-e29b-41d4-a716-446655440001",
	"name": "Phòng 101A",
	"code": "P-101A",
	"floor": 1,
	"status": "active"
}
```

**Field Descriptions**:

| Field        | Type   | Required | Validation           | Description                     |
| ------------ | ------ | -------- | -------------------- | ------------------------------- |
| apartment_id | UUID   | Yes      | Valid UUID           | ID của căn hộ                   |
| name         | string | Yes      | min 1, max 200       | Tên phòng                       |
| code         | string | Yes      | min 1, max 50        | Mã phòng (unique per apartment) |
| floor        | number | Optional | int                  | Tầng                            |
| status       | enum   | Optional | `active`, `inactive` | Default: `active`               |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "aa0e8400-e29b-41d4-a716-446655440001",
		"apartment_id": "990e8400-e29b-41d4-a716-446655440001",
		"name": "Phòng 101A",
		"code": "P-101A",
		"floor": 1,
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z"
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`:
  - `apartment_id` required, valid UUID
  - `name` required, min 1, max 200
  - `code` required, min 1, max 50
  - `floor` invalid integer nếu có
- `CONFLICT`: Code đã tồn tại trong apartment (unique constraint)
- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `code` phải unique trong cùng `apartment_id`
- `created_by` được tự động set từ `req.user.id`

---

### 3. Get Room Detail

Lấy chi tiết phòng

```http
GET /api/v1/rooms/{id}
```

**Middleware**: `verify`, `validateId`

**Path Parameters**:

| Parameter | Type | Required | Description  |
| --------- | ---- | -------- | ------------ |
| id        | UUID | Yes      | ID của phòng |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "aa0e8400-e29b-41d4-a716-446655440001",
		"apartment_id": "990e8400-e29b-41d4-a716-446655440001",
		"name": "Phòng 101A",
		"code": "P-101A",
		"floor": 1,
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z",
		"updated_at": "2024-01-20T10:00:00.000Z",
		"deleted_at": null
	}
}
```

**Permissions**: Tất cả user đã authenticate

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `NOT_FOUND`: Room không tồn tại

---

### 4. Update Room

Cập nhật thông tin phòng

```http
PUT /api/v1/rooms/{id}
```

**Middleware**: `verify`, `requireAdmin`, `queryModifier`, `validateId`,
`validateZod(RoomUpdateBodySchema)`

**Request Body**:

```json
{
	"apartment_id": "990e8400-e29b-41d4-a716-446655440001",
	"name": "Phòng 101A Updated",
	"code": "P-101A",
	"floor": 2,
	"status": "active"
}
```

**Field Descriptions**: Tất cả fields optional, validation tương tự create

**Response**: Trả về room đã cập nhật

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`: Tương tự create
- `CONFLICT`: Code đã tồn tại trong apartment (unique constraint)
- `NOT_FOUND`: Room không tồn tại
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `updated_at` được tự động set
- `updated_by` được tự động set từ `req.user.id`

---

### 5. Delete Room

Xóa phòng (soft delete)

```http
DELETE /api/v1/rooms/{id}
```

**Middleware**: `verify`, `requireAdmin`, `validateId`

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": "Successfully deleted item"
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin
- `NOT_FOUND`: Room không tồn tại

**Notes**:

- Soft delete: set `deleted_at`, `deleted_by`
- `deleted_by` được set từ `req.user.id`

---

## Bed APIs

### 1. Get Beds List

Lấy danh sách giường

```http
GET /api/v1/beds
```

**Middleware**: `verify`, `requireAdmin`, `validateQueryZod(QueryParamsSchema)`, `queryModifier`

**Query Parameters**: Tương tự resident list

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"count": 10,
		"rows": [
			{
				"id": "bb0e8400-e29b-41d4-a716-446655440001",
				"room_id": "aa0e8400-e29b-41d4-a716-446655440001",
				"name": "Giường 1",
				"code": "G-001",
				"status": "active",
				"created_at": "2024-01-15T08:30:00.000Z",
				"updated_at": "2024-01-20T10:00:00.000Z",
				"deleted_at": null
			}
		],
		"page": 1,
		"pageSize": 10
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

---

### 2. Create Bed

Tạo giường mới

```http
POST /api/v1/beds
```

**Middleware**: `verify`, `requireAdmin`, `validateZod(BedCreateBodySchema)`

**Request Body**:

```json
{
	"room_id": "aa0e8400-e29b-41d4-a716-446655440001",
	"name": "Giường 1",
	"code": "G-001",
	"status": "active"
}
```

**Field Descriptions**:

| Field   | Type   | Required | Validation           | Description                 |
| ------- | ------ | -------- | -------------------- | --------------------------- |
| room_id | UUID   | Yes      | Valid UUID           | ID của phòng                |
| name    | string | Yes      | min 1, max 200       | Tên giường                  |
| code    | string | Yes      | min 1, max 50        | Mã giường (unique per room) |
| status  | enum   | Optional | `active`, `inactive` | Default: `active`           |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "bb0e8400-e29b-41d4-a716-446655440001",
		"room_id": "aa0e8400-e29b-41d4-a716-446655440001",
		"name": "Giường 1",
		"code": "G-001",
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z"
	}
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`:
  - `room_id` required, valid UUID
  - `name` required, min 1, max 200
  - `code` required, min 1, max 50
- `CONFLICT`: Code đã tồn tại trong room (unique constraint)
- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `code` phải unique trong cùng `room_id`
- `created_by` được tự động set từ `req.user.id`

---

### 3. Get Bed Detail

Lấy chi tiết giường

```http
GET /api/v1/beds/{id}
```

**Middleware**: `verify`, `validateId`

**Path Parameters**:

| Parameter | Type | Required | Description   |
| --------- | ---- | -------- | ------------- |
| id        | UUID | Yes      | ID của giường |

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"id": "bb0e8400-e29b-41d4-a716-446655440001",
		"room_id": "aa0e8400-e29b-41d4-a716-446655440001",
		"name": "Giường 1",
		"code": "G-001",
		"status": "active",
		"created_at": "2024-01-15T08:30:00.000Z",
		"updated_at": "2024-01-20T10:00:00.000Z",
		"deleted_at": null
	}
}
```

**Permissions**: Tất cả user đã authenticate

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `NOT_FOUND`: Bed không tồn tại

---

### 4. Update Bed

Cập nhật thông tin giường

```http
PUT /api/v1/beds/{id}
```

**Middleware**: `verify`, `requireAdmin`, `queryModifier`, `validateId`,
`validateZod(BedUpdateBodySchema)`

**Request Body**:

```json
{
	"room_id": "aa0e8400-e29b-41d4-a716-446655440001",
	"name": "Giường 1 Updated",
	"code": "G-001",
	"status": "active"
}
```

**Field Descriptions**: Tất cả fields optional, validation tương tự create

**Response**: Trả về bed đã cập nhật

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `VALIDATION_ERROR`: Tương tự create
- `CONFLICT`: Code đã tồn tại trong room (unique constraint)
- `NOT_FOUND`: Bed không tồn tại
- `FORBIDDEN`: User không có quyền admin

**Notes**:

- `updated_at` được tự động set
- `updated_by` được tự động set từ `req.user.id`

---

### 5. Delete Bed

Xóa giường (soft delete)

```http
DELETE /api/v1/beds/{id}
```

**Middleware**: `verify`, `requireAdmin`, `validateId`

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": "Successfully deleted item"
}
```

**Permissions**: `admin`, `system_admin` only

**Errors**:

- `UNAUTHORIZED`: Token không hợp lệ
- `FORBIDDEN`: User không có quyền admin
- `NOT_FOUND`: Bed không tồn tại

**Notes**:

- Soft delete: set `deleted_at`, `deleted_by`
- `deleted_by` được set từ `req.user.id`

---

## Complete Workflows

### Workflow 1: Tạo Resident với Document

**Step 1: Upload file tài liệu**

```http
POST /api/v1/files
Content-Type: multipart/form-data

files: <cccd_front.jpg>
type: document
title: Mặt trước CCCD
description: Căn cước công dân mặt trước
```

**Response**:

```json
{
  "success": true,
  "trace_id": null,
  "timestamp": "2024-01-15T08:30:00.000Z",
  "errors": null,
  "data": {
    "id": "file-uuid-001",
    "name": "cccd-front-abc123.jpg",
    "original_name": "cccd_front.jpg",
    "mime": "image/jpeg",
    "size": 102400,
    "storage_provider": "minio",
    "owner_user_id": "user-uuid",
    ...
  }
}
```

**Step 2: Tạo resident**

```http
POST /api/v1/residents

{
  "full_name": "Nguyễn Văn A",
  "email": "nguyenvana@example.com",
  "phone": "+84 901 234 567",
  "id_card": "001234567890",
  "date_of_birth": "1990-05-15",
  "status": "active"
}
```

**Response**:

```json
{
  "success": true,
  "trace_id": null,
  "timestamp": "2024-01-15T08:30:00.000Z",
  "errors": null,
  "data": {
    "id": "resident-uuid-001",
    "full_name": "Nguyễn Văn A",
    ...
  }
}
```

**Step 3: Gắn document vào resident**

```http
POST /api/v1/residents/resident-uuid-001/documents

{
  "file_id": "file-uuid-001",
  "document_type": "id_card",
  "document_name": "CCCD mặt trước",
  "description": "Căn cước công dân mặt trước",
  "is_primary": true
}
```

**Response**:

```json
{
  "success": true,
  "trace_id": null,
  "timestamp": "2024-01-15T08:30:00.000Z",
  "errors": null,
  "data": {
    "id": "doc-uuid-001",
    "resident_id": "resident-uuid-001",
    "file_id": "file-uuid-001",
    "document_type": "id_card",
    ...
  }
}
```

---

### Workflow 2: Hiển thị danh sách documents với file URLs

**Step 1: Lấy danh sách documents**

```http
GET /api/v1/residents/resident-uuid-001/documents
```

**Response**:

```json
{
  "success": true,
  "trace_id": null,
  "timestamp": "2024-01-15T08:30:00.000Z",
  "errors": null,
  "data": {
    "count": 2,
    "rows": [
      {
        "id": "doc-uuid-001",
        "file_id": "file-uuid-001",
        "document_type": "id_card",
        "document_name": "CCCD mặt trước",
        ...
      },
      {
        "id": "doc-uuid-002",
        "file_id": "file-uuid-002",
        "document_type": "household_registration",
        "document_name": "Sổ hộ khẩu",
        ...
      }
    ]
  }
}
```

**Step 2: Lấy URLs cho tất cả files (batch)**

```http
POST /api/v1/files/batch-link

{
  "file_ids": ["file-uuid-001", "file-uuid-002"],
  "mode": "view"
}
```

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": [
		{
			"file_id": "file-uuid-001",
			"url": "https://minio.example.com/bucket/file1.jpg?signature=...",
			"fallback_endpoint": null,
			"expires_in": 3600
		},
		{
			"file_id": "file-uuid-002",
			"url": "https://minio.example.com/bucket/file2.jpg?signature=...",
			"fallback_endpoint": null,
			"expires_in": 3600
		}
	]
}
```

**Step 3: Frontend map URLs vào documents**

```typescript
// Frontend code example
const documents = await fetchDocuments(residentId);
const fileIds = documents.map((d) => d.file_id);
const fileUrls = await fetchBatchLinks(fileIds);

const documentsWithUrls = documents.map((doc) => ({
	...doc,
	fileUrl: fileUrls.find((f) => f.file_id === doc.file_id)?.url || null,
}));
```

---

### Workflow 3: Xem/Download document

**Step 1: Lấy URL preview**

```http
GET /api/v1/files/file-uuid-001/link?mode=view
```

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"url": "https://minio.example.com/bucket/file.jpg?signature=...",
		"fallback_endpoint": null,
		"expires_in": 3600
	}
}
```

**Step 2: Frontend mở URL trong iframe hoặc new tab**

```typescript
// Preview in iframe
<iframe src={fileUrl} />

// Or open in new tab
window.open(fileUrl, '_blank');
```

**Step 3: Download file**

```http
GET /api/v1/files/file-uuid-001/link?mode=download
```

**Response**:

```json
{
	"success": true,
	"trace_id": null,
	"timestamp": "2024-01-15T08:30:00.000Z",
	"errors": null,
	"data": {
		"url": "https://minio.example.com/bucket/file.jpg?signature=...&response-content-disposition=attachment",
		"fallback_endpoint": null,
		"expires_in": 3600
	}
}
```

**Step 4: Frontend trigger download**

```typescript
// Trigger download
window.location.href = fileUrl;

// Or use hidden link
const link = document.createElement('a');
link.href = fileUrl;
link.download = 'document.jpg';
link.click();
```

---

## Error Handling

### Standard Error Response

```json
{
	"error": {
		"code": "VALIDATION_ERROR",
		"message": "Validation failed",
		"details": [
			{
				"field": "email",
				"code": "INVALID_EMAIL",
				"messages": {
					"vi": "Email không hợp lệ",
					"en": "Invalid email format"
				}
			}
		]
	}
}
```

### Common Error Codes

| Code                  | Description                          | HTTP Status | Fields         |
| --------------------- | ------------------------------------ | ----------- | -------------- |
| `UNAUTHORIZED`        | Token không hợp lệ hoặc hết hạn      | 401         | -              |
| `FORBIDDEN`           | Không có quyền truy cập              | 403         | -              |
| `NOT_FOUND`           | Resource không tồn tại               | 404         | -              |
| `VALIDATION_ERROR`    | Dữ liệu không hợp lệ                 | 400         | field-specific |
| `CONFLICT`            | Dữ liệu conflict (unique constraint) | 409         | -              |
| `FILE_NOT_FOUND`      | File không tồn tại                   | 404         | -              |
| `FILE_UPLOAD_FAILED`  | Upload file thất bại                 | 500         | -              |
| `RATE_LIMIT_EXCEEDED` | Quá rate limit                       | 429         | -              |
| `VERSION_CONFLICT`    | Optimistic locking failure           | 409         | -              |

### Validation Error Details

**Resident Create/Update**:

- `full_name`: required, min 1, max 200
- `email`: invalid format (nếu có), max 255
- `phone`: max 25 (nếu có)
- `id_card`: max 50 (nếu có)
- `emergency_contact_name`: max 200 (nếu có)
- `emergency_contact_phone`: max 25 (nếu có)
- `emergency_contact_relationship`: max 100 (nếu có)
- `status`: invalid enum value
- `version`: invalid positive integer (update only)

**Resident Document**:

- `file_id`: required, invalid UUID
- `document_type`: required, invalid enum
- `document_name`: max 255 (nếu có)

**File Upload**:

- `files`: required (MISSING_FILE_FIELDS)
- `title`: max 150 (nếu có)
- `description`: max 500 (nếu có)
- `note`: max 255 (nếu có)

**Query Parameters**:

- `page`: invalid positive integer
- `pageSize`: invalid positive integer, max 100
- `sortOrder`: invalid enum (asc/desc)

### Conflict Errors

- `email`: Email đã tồn tại
- `phone`: Số điện thoại đã tồn tại
- `id_card`: CCCD đã tồn tại
- `file_id`: File đã được gán cho resident này
- `document_type`: Đã có tài liệu chính cho loại này

### Best Practices

1. **Luôn check `error.code`** để xử lý lỗi phù hợp
2. **Hiển thị message theo ngôn ngữ** (`messages.vi` hoặc `messages.en`)
3. **Handle soft delete**: Nếu resident có `deleted_at != null`, coi như không tồn tại
4. **Optimistic locking**: Khi update resident, luôn gửi `version` hiện tại
5. **File URL expiration**: Signed URL có TTL, không cache localStorage
6. **Batch limit**: `batch-link` tối đa 100 file IDs per request
7. **Pagination**: File list max pageSize = 50
8. **Rate limiting**: Upload có rate limit

---

## Notes for Frontend Team

### File URL Handling

- **KHÔNG lưu signed URL vào localStorage/DB** - URL sẽ expire
- **Luôn gọi `/link` hoặc `/batch-link` khi cần hiển thị/tải file**
- `thumbnail_url` trong `/files` list có thể cache ngắn (5-10 phút)
- `fallback_endpoint` dùng cho local storage hoặc khi signed URL fail
- Local storage: dùng `fallback_endpoint` thay vì signed URL

### Resident Document Display

- API resident documents chỉ trả `file_id`, KHÔNG có URL
- Dùng `POST /files/batch-link` để lấy URLs cho list documents
- Dùng `GET /files/{id}/link` để lấy URL cho single document
- Handle `error` field trong batch response (file có thể không tồn tại)

### Performance Tips

- Dùng `batch-link` thay vì gọi `/link` N lần
- Pagination cho resident list (max 100 per page)
- Pagination cho file list (max 50 per page)
- Lazy load file URLs khi user scroll đến
- Pre-load URLs cho documents đang hiển thị
- File list có concurrency limit cho MinIO calls (configurable)

### Security

- Luôn gửi `Authorization: Bearer <token>`
- User thường chỉ truy cập data của chính mình
- Admin/System Admin có full access
- File URLs có TTL, không share URL công khai
- Rate limit cho upload endpoint

### Edge Cases

1. **Resident không có chỗ ở**: `residence` sẽ là `null`
2. **Resident không có documents**: `documents` sẽ là mảng rỗng `[]`
3. **Resident không có user account**: `user` sẽ là `null`
4. **File local storage**: `thumbnail_url` và `url` sẽ là `null`, dùng `fallback_endpoint`
5. **Soft deleted resident**: Không hiển thị trong list, trả `NOT_FOUND` khi get detail
6. **Batch link với file không tồn tại**: Trả `error` field, không làm hỏng cả batch
7. **Version conflict khi update**: Trả `CONFLICT` error, cần fetch lại data và retry
8. **Unique constraint violation**: Trả `CONFLICT` với message chi tiết

---

## Support

Nếu có thắc mắc hoặc cần clarification, liên hệ Backend Team.
