| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444 |
- # 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
- ---
- ## 1.1 Resend OTP
- Gửi lại mã OTP mới đến email (hủy OTP cũ).
- ### Endpoint
- ```
- POST /apis/auth/resend-otp
- ```
- ### Request Headers
- | Header | Value | Required |
- |--------|-------|----------|
- | Content-Type | application/json | Yes |
- ### Request Body
- ```json
- {
- "email": "user@example.com",
- "lang": "lo" // Default: "lo" / en / vi
- }
- ```
- ### Response
- Tương tự **Request OTP**.
- ---
- ## 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
- ---
- ## 2.1 Google Login - Get Authorization URL
- Lấy URL để redirect người dùng đến Google OAuth consent screen.
- ### Endpoint
- ```
- POST /apis/auth/google-login
- ```
- ### Request Headers
- | Header | Value | Required |
- |--------|-------|----------|
- | Content-Type | application/json | Yes |
- ### Request Body
- ```json
- {
- "lang": "lo" // Optional: "lo" (default), "en"
- }
- ```
- ### Parameters
- | Field | Type | Required | Default | Description |
- |-------|------|----------|---------|-------------|
- | lang | string | No | "lo" | Ngôn ngữ thông báo: `lo` (ລາວ), `en` (English) |
- ### Response Success (200)
- ```json
- {
- "errorCode": "0",
- "message": "<Config: SUCCESS>",
- "data": {
- "url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx&redirect_uri=xxx&response_type=code&scope=email%20profile"
- }
- }
- ```
- ### Response Fields
- | Field | Type | Description |
- |-------|------|-------------|
- | errorCode | string | "0" = Success |
- | message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
- | data.url | string | URL để redirect user đến Google OAuth |
- ### Flow
- 1. Frontend gọi API này để lấy Google OAuth URL
- 2. Frontend redirect user đến URL nhận được
- 3. User đăng nhập Google và cho phép quyền
- 4. Google redirect về `redirect_uri` với `code` parameter
- 5. Frontend gọi API `/apis/auth/google-callback` với `code` này
- ### Response Error
- ```json
- {
- "errorCode": "-6",
- "message": "<Config: GOOGLE_CONFIG_MISSING>",
- "data": {}
- }
- ```
- ---
- ## 2.2 Google Callback - Complete Login
- Xác thực authorization code từ Google và hoàn tất đăng nhập.
- - Nếu email chưa tồn tại: Tự động tạo tài khoản mới trong `CUSTOMER_INFO`
- - Nếu email đã tồn tại: Cập nhật thông tin và đăng nhập
- ### Endpoint
- ```
- POST /apis/auth/google-callback
- ```
- ### Request Headers
- | Header | Value | Required |
- |--------|-------|----------|
- | Content-Type | application/json | Yes |
- ### Request Body
- ```json
- {
- "code": "4/0AXEWy...",
- "redirectUri": "https://your-app.com/callback",
- "lang": "lo" // Optional: "lo" (default), "en"
- }
- ```
- ### Parameters
- | Field | Type | Required | Default | Description |
- |-------|------|----------|---------|-------------|
- | code | string | Yes | - | Authorization code từ Google (nhận qua URL callback) |
- | redirectUri | string | No | - | Redirect URI đã đăng ký với Google (nếu khác default) |
- | lang | string | No | "lo" | Ngôn ngữ thông báo: `lo` (ລາວ), `en` (English) |
- ### Response Success (200) - GIỐNG API verify-otp
- ```json
- {
- "errorCode": "0",
- "message": "<Config: GOOGLE_LOGIN_SUCCESS>",
- "data": {
- "userId": 12345,
- "email": "user@gmail.com",
- "fullName": "Nguyen Van A",
- "avatarUrl": "https://lh3.googleusercontent.com/...",
- "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
- "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
- "expiresAt": "2024-12-30T10:00:00Z"
- }
- }
- ```
- ### Response Fields (giống verify-otp)
- | Field | Type | Description |
- |-------|------|-------------|
- | errorCode | string | "0" = Success |
- | message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
- | data.userId | int | ID người dùng (từ CUSTOMER_INFO.ID) |
- | data.email | string | Email người dùng |
- | data.fullName | string | Họ tên đầy đủ (từ Google profile) |
- | data.avatarUrl | string | URL ảnh đại diện từ Google |
- | data.accessToken | string | JWT access token (24 giờ) |
- | data.refreshToken | string | Refresh token (30 ngày) |
- | data.expiresAt | datetime | Thời điểm access token hết hạn |
- ### Database Operations
- Khi login thành công:
- 1. **CUSTOMER_INFO**:
- - User mới: INSERT với `SUR_NAME`, `LAST_NAME`, `EMAIL`, `AVATAR_URL`, `IS_VERIFIED=1`
- - User có sẵn: UPDATE `AVATAR_URL` (nếu trống), `LAST_LOGIN_DATE`, `IS_VERIFIED=1`
- 2. **USER_TOKEN**: Revoke tokens cũ và tạo token mới
- ### Response Error Cases
- ```json
- // Code không được cung cấp
- {
- "errorCode": "-801",
- "message": "<Config: GOOGLE_CODE_REQUIRED>",
- "data": {}
- }
- // Lỗi trao đổi token với Google
- {
- "errorCode": "-700",
- "message": "<Config: GOOGLE_TOKEN_EXCHANGE_FAILED>",
- "data": { "error": "..." }
- }
- // Không nhận được email từ Google
- {
- "errorCode": "-700",
- "message": "<Config: GOOGLE_NO_EMAIL>",
- "data": {}
- }
- // Lỗi hệ thống
- {
- "errorCode": "-6",
- "message": "<Config: SYSTEM_FAILURE>",
- "data": {}
- }
- ```
- ### 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
- - User đăng nhập qua Google tự động được đánh dấu `IS_VERIFIED = 1`
- - Nếu user chưa có avatar và Google cung cấp, sẽ tự động lưu
- ---
- ## Google OAuth Flow Diagram
- ```
- ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
- │ Client │ │ API │ │ Google │ │ Database │
- └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
- │ │ │ │
- │ POST /google-login│ │ │
- │──────────────────>│ │ │
- │ │ │ │
- │ { url: "..." } │ │ │
- │<──────────────────│ │ │
- │ │ │ │
- │ Redirect to URL │ │ │
- │──────────────────────────────────────>│ │
- │ │ │ │
- │ │ │ User Login │
- │ │ │ Grant Permission │
- │ │ │ │
- │ Callback with code│ │ │
- │<──────────────────────────────────────│ │
- │ │ │ │
- │ POST /google-callback │ │
- │ { code: "..." }│ │ │
- │──────────────────>│ │ │
- │ │ │ │
- │ │ Exchange code │ │
- │ │──────────────────>│ │
- │ │ │ │
- │ │ access_token │ │
- │ │<──────────────────│ │
- │ │ │ │
- │ │ Get user info │ │
- │ │──────────────────>│ │
- │ │ │ │
- │ │ email, name, pic │ │
- │ │<──────────────────│ │
- │ │ │ │
- │ │ Insert/Update CUSTOMER_INFO │
- │ │ Create USER_TOKEN │
- │ │──────────────────────────────────────>│
- │ │ │ │
- │ JWT Token Response │ │
- │<──────────────────│ │ │
- │ │ │ │
- ```
- ---
- ## Example Usage (cURL) - Google Login
- ### Step 1: Get Google OAuth URL
- ```bash
- curl -X POST http://149.28.132.56:8360/apis/auth/google-login \
- -H "Content-Type: application/json"
- ```
- ### Step 2: Complete Login with Code
- ```bash
- curl -X POST http://149.28.132.56:8360/apis/auth/google-callback \
- -H "Content-Type: application/json" \
- -d '{
- "code": "4/0AXEWy..."
- }'
- ```
- ---
- ## 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ệ |
- ### External Service Errors (-700 to -799)
- | errorCode| Constant | Description |
- |------|----------|-------------|
- | "-700" | ExternalServiceError | Lỗi từ dịch vụ bên ngoài (Google OAuth, etc.) |
- ### 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 (List)
- Lấy danh sách bài viết với pagination và filters.
- ### Endpoint
- ```
- POST /apis/article/load
- ```
- ### Request (tất cả bài viết)
- ```json
- {
- "lang": "lo",
- "pageNumber": 0,
- "pageSize": 10
- }
- ```
- ### Request (lọc theo category)
- ```json
- {
- "lang": "lo",
- "pageNumber": 0,
- "pageSize": 10,
- "categoryId": 1,
- "isFeatured": false
- }
- ```
- | Field | Type | Default | Description |
- |-------|------|---------|-------------|
- | lang | string | "lo" | Ngôn ngữ (hoặc header Accept-Language) |
- | pageNumber | int | 0 | Trang hiện tại (0-indexed) |
- | pageSize | int | 10 | Số item mỗi trang |
- | categoryId | int? | **null** | **null = lấy tất cả**, có giá trị = lọc theo danh mục |
- | isFeatured | bool? | null | true = chỉ bài nổi bật, null = tất cả |
- ### Response Success
- ```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": {
- "pageNumber": 0,
- "pageSize": 10,
- "totalCount": 25,
- "totalPages": 3
- }
- }
- }
- ```
- ### Response Fields - articles[]
- | Field | Type | DB Column | Description |
- |-------|------|-----------|-------------|
- | id | int | ARTICLE.ID | **ID bài viết** - dùng để lấy chi tiết qua `/apis/article/detail` |
- | 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 |
- ### Important Notes
- - **Ordering**: Danh sách sắp xếp theo:
- 1. `IS_PINNED` DESC (bài ghim lên đầu)
- 2. `PUBLISHED_DATE` DESC (mới nhất trước)
- 3. `CREATED_DATE` DESC
- - **Filter logic**:
- - `categoryId = null` → Lấy **tất cả** bài viết
- - `categoryId = 1` → Chỉ lấy bài viết thuộc category 1
- ### cURL Examples
- #### Lấy tất cả bài viết
- ```bash
- curl -X POST http://localhost:8360/apis/article/load \
- -H "Content-Type: application/json" \
- -d '{
- "lang": "lo",
- "pageNumber": 0,
- "pageSize": 10
- }'
- ```
- #### Lấy bài viết theo category
- ```bash
- curl -X POST http://localhost:8360/apis/article/load \
- -H "Content-Type: application/json" \
- -d '{
- "categoryId": 1,
- "lang": "en",
- "pageSize": 5
- }'
- ```
- #### Lấy bài viết nổi bật
- ```bash
- curl -X POST http://localhost:8360/apis/article/load \
- -H "Content-Type: application/json" \
- -d '{
- "isFeatured": true,
- "lang": "lo",
- "pageSize": 6
- }'
- ```
- ---
- ## 4.1 Article Detail
- Lấy chi tiết 1 bài viết theo ID hoặc slug.
- ### Endpoint
- ```
- POST /apis/article/detail
- ```
- ### Request (theo ID - khuyến nghị)
- ```json
- {
- "id": 1,
- "lang": "lo"
- }
- ```
- ### Request (theo slug - SEO friendly)
- ```json
- {
- "slug": "huong-dan-cai-dat-esim",
- "lang": "lo"
- }
- ```
- | Field | Type | Required | Description |
- |-------|------|----------|-------------|
- | id | int? | Conditional | ID bài viết (lấy từ list API) |
- | slug | string? | Conditional | Slug của bài viết |
- | lang | string | No | Ngôn ngữ (default: "lo") |
- **Note**: Phải có **ít nhất một** trong `id` hoặc `slug`
- ### Response Success
- ```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 đầy đủ...</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 - article
- | 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 |
- | content | string | ARTICLE.CONTENT<br/>CONTENT_EN<br/>CONTENT_LO | **Nội dung HTML đầy đủ** |
- | thumbnailUrl | string | ARTICLE.THUMBNAIL_URL | Ảnh thumbnail |
- | 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) |
- | categoryId | int | ARTICLE.CATEGORY_ID | ID danh mục |
- | viewCount | int | ARTICLE.VIEW_COUNT | Số lượt xem (**tự động +1 khi gọi API này**) |
- | isFeatured | bool | ARTICLE.IS_FEATURED | Bài viết nổi bật |
- | publishedDate | datetime | ARTICLE.PUBLISHED_DATE | Ngày xuất bản |
- | createdDate | datetime | ARTICLE.CREATED_DATE | Ngày tạo bài viết |
- ### Important Notes
- - **Auto view count**: Mỗi lần gọi API này, `viewCount` tự động +1
- - **Recommended flow**:
- 1. Gọi `/apis/article/load` → Lấy `id` từ danh sách
- 2. Gọi `/apis/article/detail` với `id` đó
- ### cURL Examples
- #### Lấy chi tiết theo ID
- ```bash
- curl -X POST http://localhost:8360/apis/article/detail \
- -H "Content-Type: application/json" \
- -d '{
- "id": 1,
- "lang": "lo"
- }'
- ```
- #### Lấy chi tiết theo slug
- ```bash
- curl -X POST http://localhost:8360/apis/article/detail \
- -H "Content-Type: application/json" \
- -d '{
- "slug": "huong-dan-cai-dat-esim",
- "lang": "en"
- }'
- ```
- ---
- ## 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 (hỗ trợ cấu trúc phân cấp).
- ### Endpoint
- ```
- POST /apis/content/faq-category
- ```
- ### Request
- ```json
- {
- "lang": "lo",
- "pageNumber": 0,
- "pageSize": 10,
- "parentId": null
- }
- ```
- | 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 |
- | parentId | int? | null | ID danh mục cha (null = lấy root categories) |
- ### 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",
- "parentId": null,
- "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 |
- | parentId | int? | FAQ_CATEGORY.PARENT_ID | **ID danh mục cha (null = danh mục gốc)** |
- | displayOrder | int | FAQ_CATEGORY.DISPLAY_ORDER | Thứ tự hiển thị |
- ### Use Cases
- - **Homepage FAQ**: `parentId = null` → Lấy danh mục gốc
- - **Support Center**: `parentId = <categoryId>` → Lấy danh mục con
- - Hỗ trợ cấu trúc phân cấp không giới hạn cấp độ
- ---
- ## 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`
- ---
- ## 9. Device eSIM Compatibility - Get Metadata
- Lấy danh sách brands và categories để hiển thị tabs/filters cho tính năng kiểm tra thiết bị.
- ### Endpoint
- ```
- GET /apis/content/device-metadata
- ```
- ### Request
- Không cần body (GET request)
- ### Response
- ```json
- {
- "errorCode": "0",
- "message": "Success",
- "data": {
- "brands": [
- {
- "brand": "Apple",
- "deviceCount": 24,
- "popularCount": 24,
- "devices": [
- {
- "id": 1,
- "modelName": "iPhone XS",
- "category": "Phone",
- "isPopular": true,
- "displayOrder": 1,
- "modelNameEn": "iPhone XS",
- "modelNameLo": "iPhone XS",
- "notes": "Tất cả phiên bản trừ China Mainland, Hong Kong, Macao",
- "notesEn": "All versions except China Mainland, Hong Kong, Macao",
- "notesLo": "ທຸກເວີຊັນຍົກເວັ້ນ China Mainland, Hong Kong, Macao"
- }
- ]
- },
- {
- "brand": "Samsung",
- "deviceCount": 19,
- "popularCount": 19,
- "devices": [...]
- }
- ],
- "categories": [
- {
- "category": "Phone",
- "deviceCount": 67
- },
- {
- "category": "Tablet",
- "deviceCount": 4
- }
- ]
- }
- }
- ```
- ### Response Fields - brands[]
- | Field | Type | Description |
- |-------|------|-------------|
- | brand | string | Tên hãng (Apple, Samsung, Google,...) |
- | deviceCount | int | Tổng số thiết bị của hãng này |
- | popularCount | int | Số thiết bị phổ biến (IS_POPULAR = 1) |
- | **devices[]** | array | **Danh sách tất cả thiết bị của brand** |
- ### Response Fields - brands[].devices[]
- | Field | Type | Description |
- |-------|------|-------------|
- | id | int | ID thiết bị |
- | modelName | string | Tên model (mặc định Vietnamese) |
- | category | string | Loại thiết bị (Phone, Tablet, Laptop, Watch) |
- | isPopular | bool | Thiết bị phổ biến |
- | displayOrder | int | Thứ tự sắp xếp |
- | modelNameEn | string | Tên model tiếng Anh |
- | modelNameLo | string | Tên model tiếng Lào |
- | notes | string | Ghi chú (Vietnamese) |
- | notesEn | string | Ghi chú (English) |
- | notesLo | string | Ghi chú (Lao) |
- ### Response Fields - categories[]
- | Field | Type | Description |
- |-------|------|-------------|
- | category | string | Loại thiết bị (Phone, Tablet, Laptop, Watch) |
- | deviceCount | int | Tổng số thiết bị thuộc category này |
- ### Use Case
- API này trả về **TẤT CẢ** devices trong một lần gọi để:
- - Frontend có thể filter/search ở client-side
- - Hiển thị tabs cho từng brand với danh sách devices
- - Không cần pagination vì dùng cho client-side filtering
- - Giảm số lượng API calls
- ### Important Notes
- - **Trả về toàn bộ devices** trong response (không phân trang)
- - Frontend tự filter theo brand/category/search
- - Chỉ devices có `STATUS = 1` và `SUPPORTS_ESIM = 1`
- - Sắp xếp theo `DISPLAY_ORDER`, `BRAND`, `MODEL_NAME`
- ---
- ## 10. Device eSIM Compatibility - Search & Filter
- Tìm kiếm và lọc danh sách thiết bị hỗ trợ eSIM.
- ### Endpoint
- ```
- POST /apis/content/device-compatibility
- ```
- ### Request Examples
- #### Example 1: Lấy tất cả devices phổ biến (Quick Lookup)
- ```json
- {
- "isPopular": true,
- "lang": "lo",
- "pageNumber": 0,
- "pageSize": 50
- }
- ```
- #### Example 2: Filter theo brand (Tab Apple)
- ```json
- {
- "brand": "Apple",
- "isPopular": true,
- "lang": "en",
- "pageNumber": 0,
- "pageSize": 50
- }
- ```
- #### Example 3: Search keyword
- ```json
- {
- "searchKeyword": "iPhone 13",
- "lang": "lo",
- "pageNumber": 0,
- "pageSize": 20
- }
- ```
- #### Example 4: Filter brand + category
- ```json
- {
- "brand": "Apple",
- "category": "Tablet",
- "lang": "en",
- "pageNumber": 0,
- "pageSize": 10
- }
- ```
- ### Request Parameters
- | Field | Type | Required | Default | Description |
- |-------|------|----------|---------|-------------|
- | brand | string | No | null | Filter theo hãng (Apple, Samsung, Google,...) |
- | category | string | No | null | Filter theo loại (Phone, Tablet, Laptop, Watch) |
- | searchKeyword | string | No | null | Tìm kiếm trong tên model (all languages) |
- | isPopular | bool | No | null | `true` = chỉ lấy thiết bị phổ biến (quick lookup) |
- | lang | string | No | "lo" | Ngôn ngữ response: "lo", "en" |
- | pageNumber | int | No | 0 | Trang hiện tại (0-indexed) |
- | pageSize | int | No | 50 | Số items/page (mặc định 50 cho device list) |
- ### Response Success
- ```json
- {
- "errorCode": "0",
- "message": "Success",
- "data": {
- "devices": [
- {
- "id": 1,
- "brand": "Apple",
- "modelName": "iPhone XS",
- "category": "Phone",
- "notes": "ທຸກເວີຊັນຍົກເວັ້ນ iPhone ຈາກ ຈີນແຜ່ນດິນໃຫຍ່, ຮ່ອງກົງ ແລະ ມາເກົາ",
- "supportsEsim": true,
- "isPopular": true,
- "displayOrder": 1
- },
- {
- "id": 2,
- "brand": "Apple",
- "modelName": "iPhone 13",
- "category": "Phone",
- "notes": null,
- "supportsEsim": true,
- "isPopular": true,
- "displayOrder": 12
- }
- ],
- "pagination": {
- "pageNumber": 0,
- "pageSize": 50,
- "totalCount": 24,
- "totalPages": 1
- }
- }
- }
- ```
- ### Response Fields - devices[]
- | Field | Type | DB Column | Description |
- |-------|------|-----------|-------------|
- | id | int | DEVICE_ESIM_COMPATIBILITY.ID | ID thiết bị (Primary Key) |
- | brand | string | DEVICE_ESIM_COMPATIBILITY.BRAND | Hãng sản xuất (Apple, Samsung,...) |
- | modelName | string | DEVICE_ESIM_COMPATIBILITY.MODEL_NAME<br/>MODEL_NAME_EN<br/>MODEL_NAME_LO | Tên model (theo ngôn ngữ được chọn) |
- | category | string | DEVICE_ESIM_COMPATIBILITY.CATEGORY | Loại thiết bị (Phone, Tablet, Laptop, Watch) |
- | notes | string | DEVICE_ESIM_COMPATIBILITY.NOTES<br/>NOTES_EN<br/>NOTES_LO | Lưu ý đặc biệt (nullable, theo ngôn ngữ) |
- | supportsEsim | bool | DEVICE_ESIM_COMPATIBILITY.SUPPORTS_ESIM | Hỗ trợ eSIM (luôn = true cho kết quả được trả về) |
- | isPopular | bool | DEVICE_ESIM_COMPATIBILITY.IS_POPULAR | Thiết bị phổ biến (hiển thị trong quick lookup) |
- | displayOrder | int | DEVICE_ESIM_COMPATIBILITY.DISPLAY_ORDER | Thứ tự sắp xếp |
- ### Filter Logic
- - **Auto-filter**: Chỉ trả về devices với `STATUS = 1` và `SUPPORTS_ESIM = 1`
- - **Search**: Tìm kiếm keyword trong `MODEL_NAME`, `MODEL_NAME_EN`, `MODEL_NAME_LO` (case-insensitive)
- - **Ordering**: `DISPLAY_ORDER` ASC → `BRAND` ASC → `MODEL_NAME` ASC
- ### cURL Examples
- #### Get metadata (brands & categories)
- ```bash
- curl -X GET http://localhost:8360/apis/content/device-metadata \
- -H "Content-Type: application/json"
- ```
- #### Search for "iPhone 13"
- ```bash
- curl -X POST http://localhost:8360/apis/content/device-compatibility \
- -H "Content-Type: application/json" \
- -d '{
- "searchKeyword": "iPhone 13",
- "lang": "en",
- "pageSize": 20
- }'
- ```
- #### Get popular Apple devices
- ```bash
- curl -X POST http://localhost:8360/apis/content/device-compatibility \
- -H "Content-Type: application/json" \
- -d '{
- "brand": "Apple",
- "isPopular": true,
- "lang": "lo",
- "pageSize": 50
- }'
- ```
- ### Implementation Notes
- - **Sample Data**: 70+ devices đã có sẵn trong `Database/DeviceCompatibility_Schema.sql`
- - **Quick Lookup**: Set `isPopular=true` để chỉ lấy thiết bị phổ biến (Apple 24 devices, Samsung 19 devices, Google Pixel 10 devices)
- - **Full Search**: Không set `isPopular` và dùng `searchKeyword` để tìm kiếm toàn bộ database
- - **Multi-language**: Notes field hỗ trợ 3 ngôn ngữ (vi, en, lo)
- ---
|