# EsimLao Authentication API Documentation

## Overview
API xác thực người dùng qua email với OTP (One-Time Password).
URL_UAT : http://149.28.132.56:8360/
---

## 1. Request OTP

Gửi mã OTP đến email người dùng để xác thực đăng nhập.

### Endpoint
```
POST /apis/auth/request-otp
```

### Request Headers
| Header | Value | Required |
|--------|-------|----------|
| Content-Type | application/json | Yes |

### Request Body
```json
{
    "email": "user@example.com",
    "lang": "lo"  // Default: "lo" / en
}
```

### Parameters
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| email | string | Yes | - | Email address của người dùng |
| lang | string | No | "vi" | Ngôn ngữ email: `vi` (Tiếng Việt), `en` (English), `lo` (ລາວ) |

### Response Success (200)
```json
{
    "errorCode": "0",
    "message": "<Config: OTP_SENT_SUCCESS>",
    "data": {
        "email": "user@example.com",
        "expireInSeconds": 300
    }
}
```

### Response Error (200)
```json
// Email không được cung cấp
{
    "errorCode": "-801",
    "message": "<Config: EMAIL_REQUIRED>",
    "data": {}
}

// Lỗi hệ thống
{
    "errorCode": "-6",
    "message": "<Config: SYSTEM_FAILURE>",
    "data": {}
}
```

### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| errorCode| string | "0" = Success, khác "0" = Error (xem Error Codes) |
| message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
| data.email | string | Email đã gửi OTP |
| data.expireInSeconds | int | Thời gian OTP hết hạn (giây) |

### Notes
- OTP gồm 6 chữ số
- OTP có hiệu lực trong 5 phút
- Mỗi lần request mới sẽ hủy các OTP cũ chưa sử dụng
- Nếu email chưa tồn tại, hệ thống tự động tạo tài khoản mới

---

## 2. Verify OTP

Xác thực mã OTP và hoàn tất đăng nhập.

### Endpoint
```
POST /apis/auth/verify-otp
```

### Request Headers
| Header | Value | Required |
|--------|-------|----------|
| Content-Type | application/json | Yes |

### Request Body
```json
{
    "email": "user@example.com",
    "otpCode": "123456",
    "lang": "lo"  // Default: "lo" / en
}
```

### Parameters
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| email | string | Yes | - | Email đã nhận OTP |
| otpCode | string | Yes | - | Mã OTP 6 số |
| lang | string | No | "lo" | Ngôn ngữ thông báo: `lo` (ລາວ), `en` (English) |

### Response Success (200)
```json
{
    "errorCode": "0",
    "message": "<Config: LOGIN_SUCCESS>",
    "data": {
        "userId": 12345,
        "email": "user@example.com",
        "fullName": "Nguyen Van A",
        "avatarUrl": "https://example.com/avatar.jpg",
        "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
        "expiresAt": "2024-12-30T10:00:00Z"
    }
}
```

### Response Error Cases
```json
// Thiếu email hoặc OTP
{
    "errorCode": "-801",
    "message": "<Config: EMAIL_OTP_REQUIRED>",
    "data": {}
}

// OTP không hợp lệ
{
    "errorCode": "-201",
    "message": "<Config: OTP_INVALID>",
    "data": {}
}

// OTP đã được sử dụng
{
    "errorCode": "-203",
    "message": "<Config: OTP_ALREADY_USED>",
    "data": {}
}

// OTP đã hết hạn
{
    "errorCode": "-202",
    "message": "<Config: OTP_EXPIRED>",
    "data": {}
}

// Không tìm thấy người dùng
{
    "errorCode": "-300",
    "message": "<Config: USER_NOT_FOUND>",
    "data": {}
}

// Lỗi hệ thống
{
    "errorCode": "-6",
    "message": "<Config: SYSTEM_FAILURE>",
    "data": {}
}
```

### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| errorCode| string | "0" = Success, khác "0" = Error (xem Error Codes) |
| message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
| data.userId | int | ID người dùng |
| data.email | string | Email người dùng |
| data.fullName | string | Họ tên đầy đủ |
| data.avatarUrl | string | URL ảnh đại diện (nullable) |
| data.accessToken | string | JWT access token |
| data.refreshToken | string | Refresh token để làm mới access token |
| data.expiresAt | datetime | Thời điểm access token hết hạn |

### Notes
- Access token có hiệu lực 24 giờ
- Refresh token có hiệu lực 30 ngày
- Mỗi lần đăng nhập thành công, các token cũ sẽ bị thu hồi

---

## Error Codes

### Success
| errorCode| Constant | Description |
|------|----------|-------------|
| "0" | Success | Thành công (mọi request thành công đều trả về "0") |

### General Errors (-1 to -99)
| errorCode| Constant | Description |
|------|----------|-------------|
| "-1" | Error | Lỗi chung |
| "-6" | SystemError | Lỗi hệ thống |

### OTP Errors (-200 to -299)
| errorCode| Constant | Description |
|------|----------|-------------|
| "-200" | OtpRequired | Yêu cầu OTP |
| "-201" | OtpInvalid | OTP không hợp lệ |
| "-202" | OtpExpired | OTP đã hết hạn |
| "-203" | OtpAlreadyUsed | OTP đã được sử dụng |
| "-204" | OtpMaxAttemptsExceeded | Vượt quá số lần thử |
| "-205" | OtpSendFailed | Gửi OTP thất bại |
| "-206" | OtpTooManyRequests | Request quá nhiều |

### User Errors (-300 to -399)
| errorCode| Constant | Description |
|------|----------|-------------|
| "-300" | UserNotFound | Không tìm thấy người dùng |
| "-304" | InvalidEmail | Email không hợp lệ |

### Validation Errors (-800 to -899)
| errorCode| Constant | Description |
|------|----------|-------------|
| "-801" | RequiredFieldMissing | Thiếu trường bắt buộc |

---

## Authentication

Sau khi đăng nhập thành công, sử dụng `accessToken` trong header cho các API yêu cầu xác thực:

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

---

## Flow Diagram

```
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │     │   API       │     │   Email     │
└──────┬──────┘     └──────┬──────┘     └──────┬──────┘
       │                   │                   │
       │ POST /request-otp │                   │
       │──────────────────>│                   │
       │                   │                   │
       │                   │ Generate OTP      │
       │                   │ Save to DB        │
       │                   │ Queue Email       │
       │                   │                   │
       │    Response       │                   │
       │<──────────────────│                   │
       │                   │                   │
       │                   │  Send OTP Email   │
       │                   │──────────────────>│
       │                   │                   │
       │                   │                   │ OTP Email
       │<──────────────────────────────────────│
       │                   │                   │
       │ POST /verify-otp  │                   │
       │──────────────────>│                   │
       │                   │                   │
       │                   │ Verify OTP        │
       │                   │ Generate JWT      │
       │                   │                   │
       │ Token Response    │                   │
       │<──────────────────│                   │
       │                   │                   │
```

---

## Example Usage (cURL)

### Request OTP
```bash
curl -X POST https://api.esimlao.com/apis/auth/request-otp \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "lang": "vi"
  }'
```

### Verify OTP
```bash
curl -X POST https://api.esimlao.com/apis/auth/verify-otp \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "otpCode": "123456"
  }'
```

### Use Token
```bash
curl -X GET https://api.esimlao.com/apis/user/profile \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```

---

## 3. Article Category

Lấy danh sách danh mục bài viết.

### Endpoint
```
POST /apis/article/category
```

### Request
```json
{
    "lang": "lo",
    "pageNumber": 0,
    "pageSize": 10,
    "parentId": null
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| lang | string | "lo" | Ngôn ngữ: "lo", "en" (có thể truyền qua header Accept-Language) |
| pageNumber | int | 0 | Trang hiện tại |
| pageSize | int | 10 | Số item mỗi trang |
| parentId | int? | null | ID danh mục cha (null = root) |

### Response Success
```json
{
    "errorCode": "0",
    "message": "Success",
    "data": {
        "categories": [
            {
                "id": 1,
                "categoryName": "Cẩm nang du lịch",
                "categorySlug": "cam-nang-du-lich",
                "description": "Mô tả...",
                "iconUrl": "/icons/travel.png",
                "parentId": null,
                "displayOrder": 1
            }
        ],
        "pagination": {
            "pageNumber": 0,
            "pageSize": 10,
            "totalCount": 5,
            "totalPages": 1
        }
    }
}
```

### Response Fields - categories[]
| Field | Type | DB Column | Description |
|-------|------|-----------|-------------|
| id | int | ARTICLE_CATEGORY.ID | ID danh mục bài viết (Primary Key) |
| categoryName | string | ARTICLE_CATEGORY.CATEGORY_NAME<br/>CATEGORY_NAME_EN<br/>CATEGORY_NAME_LO | Tên danh mục (theo ngôn ngữ được chọn) |
| categorySlug | string | ARTICLE_CATEGORY.CATEGORY_SLUG | URL-friendly slug cho danh mục |
| description | string | ARTICLE_CATEGORY.DESCRIPTION<br/>DESCRIPTION_EN<br/>DESCRIPTION_LO | Mô tả chi tiết danh mục |
| iconUrl | string | ARTICLE_CATEGORY.ICON_URL | Đường dẫn icon của danh mục |
| parentId | int? | ARTICLE_CATEGORY.PARENT_ID | ID danh mục cha (null = danh mục gốc) |
| displayOrder | int | ARTICLE_CATEGORY.DISPLAY_ORDER | Thứ tự hiển thị (số nhỏ hơn hiển thị trước) |

### Response Fields - pagination
| Field | Type | Description |
|-------|------|-------------|
| pageNumber | int | Trang hiện tại (bắt đầu từ 0) |
| pageSize | int | Số items trên mỗi trang |
| totalCount | int | Tổng số items trong database |
| totalPages | int | Tổng số trang (= ceiling(totalCount / pageSize)) |

---

## 4. Article Load

Lấy danh sách bài viết hoặc chi tiết 1 bài viết.

### Endpoint
```
POST /apis/article/load
```

### Request (danh sách)
```json
{
    "lang": "lo",
    "pageNumber": 0,
    "pageSize": 10,
    "categoryId": 1,
    "isFeatured": false
}
```

### Request (chi tiết)
```json
{
    "lang": "lo",
    "slug": "huong-dan-cai-dat-esim"
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| lang | string | "lo" | Ngôn ngữ (hoặc header Accept-Language) |
| pageNumber | int | 0 | Trang hiện tại |
| pageSize | int | 10 | Số item mỗi trang |
| categoryId | int? | null | Lọc theo danh mục |
| isFeatured | bool? | null | Lọc bài viết nổi bật |
| slug | string? | null | Slug để lấy chi tiết bài viết |

### Response Success (danh sách)
```json
{
    "errorCode": "0",
    "message": "Success",
    "data": {
        "articles": [
            {
                "id": 1,
                "title": "Hướng dẫn cài đặt eSIM",
                "slug": "huong-dan-cai-dat-esim",
                "summary": "Tóm tắt...",
                "thumbnailUrl": "/images/article1.jpg",
                "categoryId": 1,
                "viewCount": 150,
                "isFeatured": true,
                "isPinned": false,
                "publishedDate": "2024-12-25"
            }
        ],
        "pagination": {...}
    }
}
```

### Response Success (chi tiết)
```json
{
    "errorCode": "0",
    "message": "Success",
    "data": {
        "article": {
            "id": 1,
            "title": "Hướng dẫn cài đặt eSIM",
            "slug": "huong-dan-cai-dat-esim",
            "summary": "Tóm tắt...",
            "content": "<p>Nội dung HTML...</p>",
            "thumbnailUrl": "/images/article1.jpg",
            "coverImageUrl": "/images/cover1.jpg",
            "metaDescription": "SEO description",
            "metaKeywords": "esim, laos",
            "categoryId": 1,
            "viewCount": 151,
            "isFeatured": true,
            "publishedDate": "2024-12-25",
            "createdDate": "2024-12-20"
        }
    }
}
```

### Response Fields - articles[] (List)
| Field | Type | DB Column | Description |
|-------|------|-----------|-------------|
| id | int | ARTICLE.ID | ID bài viết (Primary Key) |
| title | string | ARTICLE.TITLE<br/>TITLE_EN<br/>TITLE_LO | Tiêu đề bài viết (theo ngôn ngữ) |
| slug | string | ARTICLE.SLUG | URL-friendly slug (unique) |
| summary | string | ARTICLE.SUMMARY<br/>SUMMARY_EN<br/>SUMMARY_LO | Tóm tắt ngắn gọn |
| thumbnailUrl | string | ARTICLE.THUMBNAIL_URL | Ảnh thumbnail (dùng cho list) |
| categoryId | int | ARTICLE.CATEGORY_ID | ID danh mục (FK → ARTICLE_CATEGORY) |
| viewCount | int | ARTICLE.VIEW_COUNT | Số lượt xem |
| isFeatured | bool | ARTICLE.IS_FEATURED | Bài viết nổi bật (true/false) |
| isPinned | bool | ARTICLE.IS_PINNED | Bài viết được ghim (true/false) |
| publishedDate | datetime | ARTICLE.PUBLISHED_DATE | Ngày xuất bản |

### Response Fields - article (Detail)
| Field | Type | DB Column | Description |
|-------|------|-----------|-------------|
| *Tất cả fields từ List + thêm:* | | | |
| content | string | ARTICLE.CONTENT<br/>CONTENT_EN<br/>CONTENT_LO | Nội dung HTML đầy đủ |
| coverImageUrl | string | ARTICLE.COVER_IMAGE_URL | Ảnh bìa (dùng cho detail page) |
| metaDescription | string | ARTICLE.META_DESCRIPTION<br/>META_DESCRIPTION_EN<br/>META_DESCRIPTION_LO | SEO meta description |
| metaKeywords | string | ARTICLE.META_KEYWORDS | SEO keywords (comma-separated) |
| createdDate | datetime | ARTICLE.CREATED_DATE | Ngày tạo bài viết |

---

## 5. Banner Load

Lấy danh sách banner.

### Endpoint
```
POST /apis/content/banner
```

### Request
```json
{
    "lang": "lo",
    "pageNumber": 0,
    "pageSize": 10,
    "position": "home"
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| lang | string | "lo" | Ngôn ngữ (hoặc header Accept-Language) |
| pageNumber | int | 0 | Trang hiện tại |
| pageSize | int | 10 | Số item mỗi trang |
| position | string? | null | Vị trí: "home", "sidebar"... |

### Response
```json
{
    "errorCode": "0",
    "data": {
        "banners": [
            {
                "id": 1,
                "title": "Banner Title",
                "subtitle": "Subtitle",
                "imageUrl": "/images/banner1.jpg",
                "imageMobileUrl": "/images/banner1_m.jpg",
                "linkUrl": "/promo",
                "linkTarget": "_blank",
                "position": "home",
                "displayOrder": 1
            }
        ],
        "pagination": {...}
    }
}
```

### Response Fields - banners[]
| Field | Type | DB Column | Description |
|-------|------|-----------|-------------|
| id | int | BANNER.ID | ID banner (Primary Key) |
| title | string | BANNER.TITLE<br/>TITLE_EN<br/>TITLE_LO | Tiêu đề banner (theo ngôn ngữ) |
| subtitle | string | BANNER.SUBTITLE<br/>SUBTITLE_EN<br/>SUBTITLE_LO | Phụ đề banner |
| imageUrl | string | BANNER.IMAGE_URL | Ảnh banner (desktop) |
| imageMobileUrl | string | BANNER.IMAGE_MOBILE_URL | Ảnh banner (mobile) |
| linkUrl | string | BANNER.LINK_URL | URL đích khi click banner |
| linkTarget | string | BANNER.LINK_TARGET | Target (_self, _blank,...) |
| position | string | BANNER.POSITION | Vị trí hiển thị (home, category,...) |
| displayOrder | int | BANNER.DISPLAY_ORDER | Thứ tự hiển thị |

**Lọc tự động**: Chỉ trả về banners với `STATUS = 1` và trong khoảng `START_DATE ≤ now ≤ END_DATE`

---

## 6. Customer Review Load

Lấy đánh giá của khách hàng.

### Endpoint
```
POST /apis/content/review
```

### Request
```json
{
    "lang": "lo",
    "pageNumber": 0,
    "pageSize": 10,
    "isFeatured": true
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| lang | string | "lo" | Ngôn ngữ |
| pageNumber | int | 0 | Trang |
| pageSize | int | 10 | Số item |
| isFeatured | bool? | null | Lọc review nổi bật |

### Response
```json
{
    "errorCode": "0",
    "data": {
        "reviews": [
            {
                "id": 1,
                "customerName": "Nguyen Van A",
                "avatarUrl": "/avatars/user1.jpg",
                "rating": 1,
                "reviewContent": "Dich vu rat tot...",
                "destination": "Vientiane, Laos",
                "isFeatured": true,
                "createdDate": "2024-12-25"
            }
        ],
        "pagination": {...}
    }
}
```

### Response Fields - reviews[]
| Field | Type | DB Column | Description |
|-------|------|-----------|-------------|
| id | int | CUSTOMER_REVIEW.ID | ID đánh giá (Primary Key) |
| customerName | string | CUSTOMER_REVIEW.CUSTOMER_NAME | Tên khách hàng |
| avatarUrl | string | CUSTOMER_REVIEW.AVATAR_URL | Ảnh đại diện (nullable) |
| rating | int | CUSTOMER_REVIEW.RATING | Số sao (1-5) |
| reviewContent | string | CUSTOMER_REVIEW.REVIEW_CONTENT<br/>REVIEW_CONTENT_EN<br/>REVIEW_CONTENT_LO | Nội dung đánh giá (theo ngôn ngữ) |
| destination | string | CUSTOMER_REVIEW.DESTINATION<br/>DESTINATION_EN<br/>DESTINATION_LO | Địa điểm du lịch |
| isFeatured | bool | CUSTOMER_REVIEW.IS_FEATURED | Review nổi bật |
| createdDate | datetime | CUSTOMER_REVIEW.CREATED_DATE | Ngày tạo review |

**Lọc tự động**: Chỉ trả về reviews với `STATUS = 1` (đã duyệt)

---

## 6.1 Customer Review Create

Khách hàng gửi đánh giá (chờ duyệt).

### Endpoint
```
POST /apis/content/review/create
```

### Request
```json
{
    "lang": "lo",
    "customerName": "Nguyen Van A",
    "reviewContent": "Dich vu rat tot, toi rat hai long!",
    "destination": "Vientiane, Laos",
    "rating": 5
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| lang | string | No | Ngôn ngữ |
| customerName | string | Yes | Tên khách hàng |
| reviewContent | string | Yes | Nội dung đánh giá |
| destination | string | No | Địa điểm |
| rating | int | No | Đánh giá (1-5) |

### Response Success
```json
{
    "errorCode": "0",
    "message": "Review submitted successfully",
    "data": {
        "reviewId": 123
    }
}
```

### Note
- Review mới sẽ có `Status = false` (chờ admin duyệt)

---

## 7. FAQ Category Load

Lấy danh mục FAQ.

### Endpoint
```
POST /apis/content/faq-category
```

### Request
```json
{
    "lang": "lo",
    "pageNumber": 0,
    "pageSize": 10
}
```

### Response
```json
{
    "errorCode": "0",
    "data": {
        "categories": [
            {
                "id": 1,
                "categoryName": "Cài đặt eSIM",
                "categorySlug": "cai-dat-esim",
                "description": "Hướng dẫn cài đặt",
                "iconUrl": "/icons/setup.png",
                "displayOrder": 1
            }
        ],
        "pagination": {...}
    }
}
```

### Response Fields - categories[]
| Field | Type | DB Column | Description |
|-------|------|-----------|-------------|
| id | int | FAQ_CATEGORY.ID | ID danh mục FAQ (Primary Key) |
| categoryName | string | FAQ_CATEGORY.CATEGORY_NAME<br/>CATEGORY_NAME_EN<br/>CATEGORY_NAME_LO | Tên danh mục FAQ |
| categorySlug | string | FAQ_CATEGORY.CATEGORY_SLUG | URL-friendly slug |
| description | string | FAQ_CATEGORY.DESCRIPTION<br/>DESCRIPTION_EN<br/>DESCRIPTION_LO | Mô tả danh mục |
| iconUrl | string | FAQ_CATEGORY.ICON_URL | Icon của danh mục |
| displayOrder | int | FAQ_CATEGORY.DISPLAY_ORDER | Thứ tự hiển thị |

---

## 8. FAQ Load

Lấy danh sách câu hỏi thường gặp.

### Endpoint
```
POST /apis/content/faq
```

### Request
```json
{
    "lang": "lo",
    "pageNumber": 0,
    "pageSize": 10,
    "categoryId": 1,
    "isFeatured": false
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| lang | string | "lo" | Ngôn ngữ |
| pageNumber | int | 0 | Trang |
| pageSize | int | 10 | Số item |
| categoryId | int? | null | Lọc theo danh mục |
| isFeatured | bool? | null | Lọc FAQ nổi bật |

### Response
```json
{
    "errorCode": "0",
    "data": {
        "faqs": [
            {
                "id": 1,
                "question": "Làm sao để cài đặt eSIM?",
                "answer": "<p>Hướng dẫn chi tiết...</p>",
                "categoryId": 1,
                "viewCount": 100,
                "isFeatured": true
            }
        ],
        "pagination": {...}
    }
}
```

### Response Fields - faqs[]
| Field | Type | DB Column | Description |
|-------|------|-----------|-------------|
| id | int | FAQ.ID | ID câu hỏi (Primary Key) |
| question | string | FAQ.QUESTION<br/>QUESTION_EN<br/>QUESTION_LO | Câu hỏi (theo ngôn ngữ) |
| answer | string | FAQ.ANSWER<br/>ANSWER_EN<br/>ANSWER_LO | Câu trả lời (HTML format) |
| categoryId | int | FAQ.CATEGORY_ID | ID danh mục (FK → FAQ_CATEGORY) |
| viewCount | int | FAQ.VIEW_COUNT | Số lượt xem |
| isFeatured | bool | FAQ.IS_FEATURED | FAQ nổi bật |

**Lọc tự động**: Chỉ trả về FAQs với `STATUS = 1`

---
