# Posts API — Luồng chi tiết cho Frontend

Tài liệu này mô tả chi tiết các endpoint liên quan đến module **Posts** dành cho team Frontend: đường dẫn, quy tắc hiển thị theo vai trò, body/response mẫu, query params (pagination / filters) và các lưu ý khi triển khai UI.

> Tham khảo schema & đường dẫn backend: [src/contracts/post/schema.ts](src/contracts/post/schema.ts), [src/contracts/post/paths.ts](src/contracts/post/paths.ts)

---

## Tổng quan endpoints

- **GET** `/api/v1/posts` — Lấy danh sách (role-filtered).
  - Mô tả: trả về danh sách phân trang; server sẽ lọc theo quyền/địa điểm người dùng.
  - Quyền: token Bearer (Authorization) được sử dụng để xác định role; nếu không có token server sẽ xử lý như unauthenticated (xem phần Luồng truy cập).

- **POST** `/api/v1/posts` — Tạo bài viết (Admin only).

- **GET** `/api/v1/posts/{id}` — Lấy chi tiết một bài.

- **PUT** `/api/v1/posts/{id}` — Cập nhật bài (Admin only).

- **DELETE** `/api/v1/posts/{id}` — Soft-delete bài (Admin only).

---

## Luồng truy cập (visibility rules)

- Role **admin** / **system_admin**: có thể xem bài ở mọi `status` (draft, published, archived). Khi frontend dùng token của admin, server sẽ không ép filter status = 'published'.
- Người dùng bình thường (resident): chỉ thấy bài `status = 'published'` (mặc định) nhưng được lọc thêm theo chuỗi vị trí: user → resident → resident_stays → bed → room → apartment → building. Server sẽ trả các bài: `target_type = 'all'` hoặc các bài target trùng với building/apartment/room của resident.
- Unauthenticated (không có token): server sẽ trả **chỉ** các bài `target_type = 'all'` và (mặc định) `status = 'published'`.

Ghi chú đặc biệt:
- `target_type = 'floor'` được xử lý như `apartment` ở backend (xem provider logic).
- Để admin hiển thị draft/archived, FE chỉ cần gửi Authorization token của admin; không cần thêm parameter đặc biệt.

---

## Query params (pagination / filtering)

Các query param chung (theo `QueryParamsSchema`):
- `page` (number, default 1)
- `pageSize` (number, default 10, max 100)
- `sortField` (string)
- `sortOrder` (`asc` | `desc`)
- `filters` (string) — filter theo syntax của `sequelize-api-paginate`.

Ví dụ filter:
- `filters=status==published`
- `filters=status==draft` (admin only)
- `filters=created_at>=2024-01-01T00:00:00Z,created_at<=2024-12-31T23:59:59Z`

Ví dụ request list (published, trang 1, 20 item, sắp xếp theo published_at desc):

```bash
curl -X GET "https://api.example.com/api/v1/posts?filters=status==published&page=1&pageSize=20&sortField=published_at&sortOrder=desc" \
  -H "Authorization: Bearer $TOKEN"
```

Lưu ý: FE thường không cần (và không nên) tự filter theo target_ids cho resident — backend sẽ trả các bài phù hợp với user dựa trên token.

---

## Body — Create / Update

Request create (`POST /api/v1/posts`) — tất cả trường theo `PostCreateBodySchema` (example):

```json
{
  "title": "Thông báo bảo trì hệ thống",
  "content": "Hệ thống sẽ bảo trì vào 22:00 - 23:00",
  "targetType": "all",
  "targetIds": [],
  "isFeatured": false,
  "status": "published",
  "publishedAt": "2026-05-21T22:00:00Z"
}
```

Rules quan trọng:
- Nếu `targetType !== 'all'` thì `targetIds` phải khác rỗng (validated server-side).
- `status` có thể là: `draft`, `published`, `archived`.

Update (`PUT /api/v1/posts/{id}`): các field là optional; `content` có thể null.

---

## Response format (common)

All responses dùng envelope theo `ApiResponseSchema`:

```json
{
  "success": true,
  "data": { /* payload */ },
  "trace_id": null,
  "timestamp": "2026-05-21T10:00:00Z",
  "errors": null
}
```

- List (`GET /api/v1/posts`) -> `data` shape:

```json
{
  "count": 123,
  "rows": [ /* PostListItem */ ],
  "page": 1,
  "pageSize": 20
}
```

- Post list item fields (`PostListItem`): `id`, `title`, `target_type`, `status`, `is_featured`, `author_id`, `author_name`, `published_at`, `created_at`, `updated_at`.

- Detail (`GET /api/v1/posts/{id}`) -> `PostDetail`: includes `content`, `target_ids` (array), `author` (object with `id`, `name`, `email`).

Ví dụ detail response `data`:

```json
{
  "id":"...",
  "title":"...",
  "content":"...",
  "target_type":"building",
  "target_ids":["uuid-1","uuid-2"],
  "status":"published",
  "is_featured":false,
  "author_id":"...",
  "author":{"id":"...","name":"Nguyễn A","email":"a@example.com"},
  "published_at":"2026-05-21T...",
  "created_at":"...",
  "updated_at":"..."
}
```

---

## Các luồng UI (Frontend)

1. Feed công khai (visitor):
   - Gọi `GET /api/v1/posts` **không có token** → backend trả `target_type == 'all'` & `status == 'published'`.
   - Hiển thị title, excerpt, author_name, published_at.

2. Resident logged-in (thường dùng app resident):
   - Gọi `GET /api/v1/posts` với `Authorization` header.
   - Backend sẽ tự resolve resident → room/apartment/building và trả những bài target phù hợp (kết hợp `post_targets`), chỉ `status = 'published'`.

3. Admin panel (create/update/delete/manage):
   - Gọi `GET /api/v1/posts` với admin token → backend không ép status = 'published' nên admin có thể thấy draft/archived.
   - Tạo bài: `POST /api/v1/posts` (admin only). FE form: chọn `targetType` và khi chọn `'building'|'apartment'|'floor'|'room'` thì show multi-select để pick `targetIds`.
   - Update: `PUT /api/v1/posts/{id}`. Nếu thay đổi `targetType` từ 'all' → non-all cần cập nhật `targetIds`.
   - Delete: `DELETE /api/v1/posts/{id}` sẽ soft-delete (FE có thể gọi và sau đó refresh list).

---

## Ví dụ cURL

- Lấy danh sách (visitor)

```bash
curl "https://api.example.com/api/v1/posts?page=1&pageSize=10"
```

- Lấy danh sách (admin xem draft)

```bash
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  "https://api.example.com/api/v1/posts?filters=status==draft&page=1&pageSize=20"
```

- Tạo bài (admin)

```bash
curl -X POST "https://api.example.com/api/v1/posts" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Tít test","targetType":"all","targetIds":[],"status":"draft"}'
```

---

## Lưu ý kỹ thuật / Implementation tips

- Backend lưu `post_targets` trong bảng `post_targets` (mapping). Khi tạo/update, server thực hiện trong transaction nên FE không cần retry đặc biệt.
- Soft-delete: `DELETE /api/v1/posts/{id}` chỉ set `deleted_at` + `deleted_by`. Để khôi phục không có endpoint công khai — phải check bên backend nếu cần.
- Khi cần lọc theo `target_id` ở client (nếu có use-case đặc biệt), trao đổi với backend để hỗ trợ query phù hợp vì việc join `post_targets` đã được backend xử lý trong visibility logic.

---

Nếu muốn, mình có thể mở rộng phần này bằng các ví dụ React/Redux (service calls + typings), hoặc tạo Postman collection / Swagger UI snippet để FE import trực tiếp. Muốn mình làm phần nào tiếp theo?
