api_auth_otp_Esim.txt 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444
  1. # EsimLao Authentication API Documentation
  2. ## Overview
  3. API xác thực người dùng qua email với OTP (One-Time Password).
  4. URL_UAT : http://149.28.132.56:8360/
  5. ---
  6. ## 1. Request OTP
  7. Gửi mã OTP đến email người dùng để xác thực đăng nhập.
  8. ### Endpoint
  9. ```
  10. POST /apis/auth/request-otp
  11. ```
  12. ### Request Headers
  13. | Header | Value | Required |
  14. |--------|-------|----------|
  15. | Content-Type | application/json | Yes |
  16. ### Request Body
  17. ```json
  18. {
  19. "email": "user@example.com",
  20. "lang": "lo" // Default: "lo" / en
  21. }
  22. ```
  23. ### Parameters
  24. | Field | Type | Required | Default | Description |
  25. |-------|------|----------|---------|-------------|
  26. | email | string | Yes | - | Email address của người dùng |
  27. | lang | string | No | "vi" | Ngôn ngữ email: `vi` (Tiếng Việt), `en` (English), `lo` (ລາວ) |
  28. ### Response Success (200)
  29. ```json
  30. {
  31. "errorCode": "0",
  32. "message": "<Config: OTP_SENT_SUCCESS>",
  33. "data": {
  34. "email": "user@example.com",
  35. "expireInSeconds": 300
  36. }
  37. }
  38. ```
  39. ### Response Error (200)
  40. ```json
  41. // Email không được cung cấp
  42. {
  43. "errorCode": "-801",
  44. "message": "<Config: EMAIL_REQUIRED>",
  45. "data": {}
  46. }
  47. // Lỗi hệ thống
  48. {
  49. "errorCode": "-6",
  50. "message": "<Config: SYSTEM_FAILURE>",
  51. "data": {}
  52. }
  53. ```
  54. ### Response Fields
  55. | Field | Type | Description |
  56. |-------|------|-------------|
  57. | errorCode| string | "0" = Success, khác "0" = Error (xem Error Codes) |
  58. | message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
  59. | data.email | string | Email đã gửi OTP |
  60. | data.expireInSeconds | int | Thời gian OTP hết hạn (giây) |
  61. ### Notes
  62. - OTP gồm 6 chữ số
  63. - OTP có hiệu lực trong 5 phút
  64. - Mỗi lần request mới sẽ hủy các OTP cũ chưa sử dụng
  65. - Nếu email chưa tồn tại, hệ thống tự động tạo tài khoản mới
  66. ---
  67. ## 1.1 Resend OTP
  68. Gửi lại mã OTP mới đến email (hủy OTP cũ).
  69. ### Endpoint
  70. ```
  71. POST /apis/auth/resend-otp
  72. ```
  73. ### Request Headers
  74. | Header | Value | Required |
  75. |--------|-------|----------|
  76. | Content-Type | application/json | Yes |
  77. ### Request Body
  78. ```json
  79. {
  80. "email": "user@example.com",
  81. "lang": "lo" // Default: "lo" / en / vi
  82. }
  83. ```
  84. ### Response
  85. Tương tự **Request OTP**.
  86. ---
  87. ## 2. Verify OTP
  88. Xác thực mã OTP và hoàn tất đăng nhập.
  89. ### Endpoint
  90. ```
  91. POST /apis/auth/verify-otp
  92. ```
  93. ### Request Headers
  94. | Header | Value | Required |
  95. |--------|-------|----------|
  96. | Content-Type | application/json | Yes |
  97. ### Request Body
  98. ```json
  99. {
  100. "email": "user@example.com",
  101. "otpCode": "123456",
  102. "lang": "lo" // Default: "lo" / en
  103. }
  104. ```
  105. ### Parameters
  106. | Field | Type | Required | Default | Description |
  107. |-------|------|----------|---------|-------------|
  108. | email | string | Yes | - | Email đã nhận OTP |
  109. | otpCode | string | Yes | - | Mã OTP 6 số |
  110. | lang | string | No | "lo" | Ngôn ngữ thông báo: `lo` (ລາວ), `en` (English) |
  111. ### Response Success (200)
  112. ```json
  113. {
  114. "errorCode": "0",
  115. "message": "<Config: LOGIN_SUCCESS>",
  116. "data": {
  117. "userId": 12345,
  118. "email": "user@example.com",
  119. "fullName": "Nguyen Van A",
  120. "avatarUrl": "https://example.com/avatar.jpg",
  121. "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  122. "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
  123. "expiresAt": "2024-12-30T10:00:00Z"
  124. }
  125. }
  126. ```
  127. ### Response Error Cases
  128. ```json
  129. // Thiếu email hoặc OTP
  130. {
  131. "errorCode": "-801",
  132. "message": "<Config: EMAIL_OTP_REQUIRED>",
  133. "data": {}
  134. }
  135. // OTP không hợp lệ
  136. {
  137. "errorCode": "-201",
  138. "message": "<Config: OTP_INVALID>",
  139. "data": {}
  140. }
  141. // OTP đã được sử dụng
  142. {
  143. "errorCode": "-203",
  144. "message": "<Config: OTP_ALREADY_USED>",
  145. "data": {}
  146. }
  147. // OTP đã hết hạn
  148. {
  149. "errorCode": "-202",
  150. "message": "<Config: OTP_EXPIRED>",
  151. "data": {}
  152. }
  153. // Không tìm thấy người dùng
  154. {
  155. "errorCode": "-300",
  156. "message": "<Config: USER_NOT_FOUND>",
  157. "data": {}
  158. }
  159. // Lỗi hệ thống
  160. {
  161. "errorCode": "-6",
  162. "message": "<Config: SYSTEM_FAILURE>",
  163. "data": {}
  164. }
  165. ```
  166. ### Response Fields
  167. | Field | Type | Description |
  168. |-------|------|-------------|
  169. | errorCode| string | "0" = Success, khác "0" = Error (xem Error Codes) |
  170. | message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
  171. | data.userId | int | ID người dùng |
  172. | data.email | string | Email người dùng |
  173. | data.fullName | string | Họ tên đầy đủ |
  174. | data.avatarUrl | string | URL ảnh đại diện (nullable) |
  175. | data.accessToken | string | JWT access token |
  176. | data.refreshToken | string | Refresh token để làm mới access token |
  177. | data.expiresAt | datetime | Thời điểm access token hết hạn |
  178. ### Notes
  179. - Access token có hiệu lực 24 giờ
  180. - Refresh token có hiệu lực 30 ngày
  181. - Mỗi lần đăng nhập thành công, các token cũ sẽ bị thu hồi
  182. ---
  183. ## 2.1 Google Login - Get Authorization URL
  184. Lấy URL để redirect người dùng đến Google OAuth consent screen.
  185. ### Endpoint
  186. ```
  187. POST /apis/auth/google-login
  188. ```
  189. ### Request Headers
  190. | Header | Value | Required |
  191. |--------|-------|----------|
  192. | Content-Type | application/json | Yes |
  193. ### Request Body
  194. ```json
  195. {
  196. "lang": "lo" // Optional: "lo" (default), "en"
  197. }
  198. ```
  199. ### Parameters
  200. | Field | Type | Required | Default | Description |
  201. |-------|------|----------|---------|-------------|
  202. | lang | string | No | "lo" | Ngôn ngữ thông báo: `lo` (ລາວ), `en` (English) |
  203. ### Response Success (200)
  204. ```json
  205. {
  206. "errorCode": "0",
  207. "message": "<Config: SUCCESS>",
  208. "data": {
  209. "url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx&redirect_uri=xxx&response_type=code&scope=email%20profile"
  210. }
  211. }
  212. ```
  213. ### Response Fields
  214. | Field | Type | Description |
  215. |-------|------|-------------|
  216. | errorCode | string | "0" = Success |
  217. | message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
  218. | data.url | string | URL để redirect user đến Google OAuth |
  219. ### Flow
  220. 1. Frontend gọi API này để lấy Google OAuth URL
  221. 2. Frontend redirect user đến URL nhận được
  222. 3. User đăng nhập Google và cho phép quyền
  223. 4. Google redirect về `redirect_uri` với `code` parameter
  224. 5. Frontend gọi API `/apis/auth/google-callback` với `code` này
  225. ### Response Error
  226. ```json
  227. {
  228. "errorCode": "-6",
  229. "message": "<Config: GOOGLE_CONFIG_MISSING>",
  230. "data": {}
  231. }
  232. ```
  233. ---
  234. ## 2.2 Google Callback - Complete Login
  235. Xác thực authorization code từ Google và hoàn tất đăng nhập.
  236. - Nếu email chưa tồn tại: Tự động tạo tài khoản mới trong `CUSTOMER_INFO`
  237. - Nếu email đã tồn tại: Cập nhật thông tin và đăng nhập
  238. ### Endpoint
  239. ```
  240. POST /apis/auth/google-callback
  241. ```
  242. ### Request Headers
  243. | Header | Value | Required |
  244. |--------|-------|----------|
  245. | Content-Type | application/json | Yes |
  246. ### Request Body
  247. ```json
  248. {
  249. "code": "4/0AXEWy...",
  250. "redirectUri": "https://your-app.com/callback",
  251. "lang": "lo" // Optional: "lo" (default), "en"
  252. }
  253. ```
  254. ### Parameters
  255. | Field | Type | Required | Default | Description |
  256. |-------|------|----------|---------|-------------|
  257. | code | string | Yes | - | Authorization code từ Google (nhận qua URL callback) |
  258. | redirectUri | string | No | - | Redirect URI đã đăng ký với Google (nếu khác default) |
  259. | lang | string | No | "lo" | Ngôn ngữ thông báo: `lo` (ລາວ), `en` (English) |
  260. ### Response Success (200) - GIỐNG API verify-otp
  261. ```json
  262. {
  263. "errorCode": "0",
  264. "message": "<Config: GOOGLE_LOGIN_SUCCESS>",
  265. "data": {
  266. "userId": 12345,
  267. "email": "user@gmail.com",
  268. "fullName": "Nguyen Van A",
  269. "avatarUrl": "https://lh3.googleusercontent.com/...",
  270. "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  271. "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
  272. "expiresAt": "2024-12-30T10:00:00Z"
  273. }
  274. }
  275. ```
  276. ### Response Fields (giống verify-otp)
  277. | Field | Type | Description |
  278. |-------|------|-------------|
  279. | errorCode | string | "0" = Success |
  280. | message | string | Thông báo từ CONFIG table (theo ngôn ngữ) |
  281. | data.userId | int | ID người dùng (từ CUSTOMER_INFO.ID) |
  282. | data.email | string | Email người dùng |
  283. | data.fullName | string | Họ tên đầy đủ (từ Google profile) |
  284. | data.avatarUrl | string | URL ảnh đại diện từ Google |
  285. | data.accessToken | string | JWT access token (24 giờ) |
  286. | data.refreshToken | string | Refresh token (30 ngày) |
  287. | data.expiresAt | datetime | Thời điểm access token hết hạn |
  288. ### Database Operations
  289. Khi login thành công:
  290. 1. **CUSTOMER_INFO**:
  291. - User mới: INSERT với `SUR_NAME`, `LAST_NAME`, `EMAIL`, `AVATAR_URL`, `IS_VERIFIED=1`
  292. - User có sẵn: UPDATE `AVATAR_URL` (nếu trống), `LAST_LOGIN_DATE`, `IS_VERIFIED=1`
  293. 2. **USER_TOKEN**: Revoke tokens cũ và tạo token mới
  294. ### Response Error Cases
  295. ```json
  296. // Code không được cung cấp
  297. {
  298. "errorCode": "-801",
  299. "message": "<Config: GOOGLE_CODE_REQUIRED>",
  300. "data": {}
  301. }
  302. // Lỗi trao đổi token với Google
  303. {
  304. "errorCode": "-700",
  305. "message": "<Config: GOOGLE_TOKEN_EXCHANGE_FAILED>",
  306. "data": { "error": "..." }
  307. }
  308. // Không nhận được email từ Google
  309. {
  310. "errorCode": "-700",
  311. "message": "<Config: GOOGLE_NO_EMAIL>",
  312. "data": {}
  313. }
  314. // Lỗi hệ thống
  315. {
  316. "errorCode": "-6",
  317. "message": "<Config: SYSTEM_FAILURE>",
  318. "data": {}
  319. }
  320. ```
  321. ### Notes
  322. - Access token có hiệu lực 24 giờ
  323. - Refresh token có hiệu lực 30 ngày
  324. - Mỗi lần đăng nhập thành công, các token cũ sẽ bị thu hồi
  325. - User đăng nhập qua Google tự động được đánh dấu `IS_VERIFIED = 1`
  326. - Nếu user chưa có avatar và Google cung cấp, sẽ tự động lưu
  327. ---
  328. ## Google OAuth Flow Diagram
  329. ```
  330. ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
  331. │ Client │ │ API │ │ Google │ │ Database │
  332. └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
  333. │ │ │ │
  334. │ POST /google-login│ │ │
  335. │──────────────────>│ │ │
  336. │ │ │ │
  337. │ { url: "..." } │ │ │
  338. │<──────────────────│ │ │
  339. │ │ │ │
  340. │ Redirect to URL │ │ │
  341. │──────────────────────────────────────>│ │
  342. │ │ │ │
  343. │ │ │ User Login │
  344. │ │ │ Grant Permission │
  345. │ │ │ │
  346. │ Callback with code│ │ │
  347. │<──────────────────────────────────────│ │
  348. │ │ │ │
  349. │ POST /google-callback │ │
  350. │ { code: "..." }│ │ │
  351. │──────────────────>│ │ │
  352. │ │ │ │
  353. │ │ Exchange code │ │
  354. │ │──────────────────>│ │
  355. │ │ │ │
  356. │ │ access_token │ │
  357. │ │<──────────────────│ │
  358. │ │ │ │
  359. │ │ Get user info │ │
  360. │ │──────────────────>│ │
  361. │ │ │ │
  362. │ │ email, name, pic │ │
  363. │ │<──────────────────│ │
  364. │ │ │ │
  365. │ │ Insert/Update CUSTOMER_INFO │
  366. │ │ Create USER_TOKEN │
  367. │ │──────────────────────────────────────>│
  368. │ │ │ │
  369. │ JWT Token Response │ │
  370. │<──────────────────│ │ │
  371. │ │ │ │
  372. ```
  373. ---
  374. ## Example Usage (cURL) - Google Login
  375. ### Step 1: Get Google OAuth URL
  376. ```bash
  377. curl -X POST http://149.28.132.56:8360/apis/auth/google-login \
  378. -H "Content-Type: application/json"
  379. ```
  380. ### Step 2: Complete Login with Code
  381. ```bash
  382. curl -X POST http://149.28.132.56:8360/apis/auth/google-callback \
  383. -H "Content-Type: application/json" \
  384. -d '{
  385. "code": "4/0AXEWy..."
  386. }'
  387. ```
  388. ---
  389. ## Error Codes
  390. ### Success
  391. | errorCode| Constant | Description |
  392. |------|----------|-------------|
  393. | "0" | Success | Thành công (mọi request thành công đều trả về "0") |
  394. ### General Errors (-1 to -99)
  395. | errorCode| Constant | Description |
  396. |------|----------|-------------|
  397. | "-1" | Error | Lỗi chung |
  398. | "-6" | SystemError | Lỗi hệ thống |
  399. ### OTP Errors (-200 to -299)
  400. | errorCode| Constant | Description |
  401. |------|----------|-------------|
  402. | "-200" | OtpRequired | Yêu cầu OTP |
  403. | "-201" | OtpInvalid | OTP không hợp lệ |
  404. | "-202" | OtpExpired | OTP đã hết hạn |
  405. | "-203" | OtpAlreadyUsed | OTP đã được sử dụng |
  406. | "-204" | OtpMaxAttemptsExceeded | Vượt quá số lần thử |
  407. | "-205" | OtpSendFailed | Gửi OTP thất bại |
  408. | "-206" | OtpTooManyRequests | Request quá nhiều |
  409. ### User Errors (-300 to -399)
  410. | errorCode| Constant | Description |
  411. |------|----------|-------------|
  412. | "-300" | UserNotFound | Không tìm thấy người dùng |
  413. | "-304" | InvalidEmail | Email không hợp lệ |
  414. ### External Service Errors (-700 to -799)
  415. | errorCode| Constant | Description |
  416. |------|----------|-------------|
  417. | "-700" | ExternalServiceError | Lỗi từ dịch vụ bên ngoài (Google OAuth, etc.) |
  418. ### Validation Errors (-800 to -899)
  419. | errorCode| Constant | Description |
  420. |------|----------|-------------|
  421. | "-801" | RequiredFieldMissing | Thiếu trường bắt buộc |
  422. ---
  423. ## Authentication
  424. 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:
  425. ```
  426. Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  427. ```
  428. ---
  429. ## Flow Diagram
  430. ```
  431. ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
  432. │ Client │ │ API │ │ Email │
  433. └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
  434. │ │ │
  435. │ POST /request-otp │ │
  436. │──────────────────>│ │
  437. │ │ │
  438. │ │ Generate OTP │
  439. │ │ Save to DB │
  440. │ │ Queue Email │
  441. │ │ │
  442. │ Response │ │
  443. │<──────────────────│ │
  444. │ │ │
  445. │ │ Send OTP Email │
  446. │ │──────────────────>│
  447. │ │ │
  448. │ │ │ OTP Email
  449. │<──────────────────────────────────────│
  450. │ │ │
  451. │ POST /verify-otp │ │
  452. │──────────────────>│ │
  453. │ │ │
  454. │ │ Verify OTP │
  455. │ │ Generate JWT │
  456. │ │ │
  457. │ Token Response │ │
  458. │<──────────────────│ │
  459. │ │ │
  460. ```
  461. ---
  462. ## Example Usage (cURL)
  463. ### Request OTP
  464. ```bash
  465. curl -X POST https://api.esimlao.com/apis/auth/request-otp \
  466. -H "Content-Type: application/json" \
  467. -d '{
  468. "email": "user@example.com",
  469. "lang": "vi"
  470. }'
  471. ```
  472. ### Verify OTP
  473. ```bash
  474. curl -X POST https://api.esimlao.com/apis/auth/verify-otp \
  475. -H "Content-Type: application/json" \
  476. -d '{
  477. "email": "user@example.com",
  478. "otpCode": "123456"
  479. }'
  480. ```
  481. ### Use Token
  482. ```bash
  483. curl -X GET https://api.esimlao.com/apis/user/profile \
  484. -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  485. ```
  486. ---
  487. ## 3. Article Category
  488. Lấy danh sách danh mục bài viết.
  489. ### Endpoint
  490. ```
  491. POST /apis/article/category
  492. ```
  493. ### Request
  494. ```json
  495. {
  496. "lang": "lo",
  497. "pageNumber": 0,
  498. "pageSize": 10,
  499. "parentId": null
  500. }
  501. ```
  502. | Field | Type | Default | Description |
  503. |-------|------|---------|-------------|
  504. | lang | string | "lo" | Ngôn ngữ: "lo", "en" (có thể truyền qua header Accept-Language) |
  505. | pageNumber | int | 0 | Trang hiện tại |
  506. | pageSize | int | 10 | Số item mỗi trang |
  507. | parentId | int? | null | ID danh mục cha (null = root) |
  508. ### Response Success
  509. ```json
  510. {
  511. "errorCode": "0",
  512. "message": "Success",
  513. "data": {
  514. "categories": [
  515. {
  516. "id": 1,
  517. "categoryName": "Cẩm nang du lịch",
  518. "categorySlug": "cam-nang-du-lich",
  519. "description": "Mô tả...",
  520. "iconUrl": "/icons/travel.png",
  521. "parentId": null,
  522. "displayOrder": 1
  523. }
  524. ],
  525. "pagination": {
  526. "pageNumber": 0,
  527. "pageSize": 10,
  528. "totalCount": 5,
  529. "totalPages": 1
  530. }
  531. }
  532. }
  533. ```
  534. ### Response Fields - categories[]
  535. | Field | Type | DB Column | Description |
  536. |-------|------|-----------|-------------|
  537. | id | int | ARTICLE_CATEGORY.ID | ID danh mục bài viết (Primary Key) |
  538. | 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) |
  539. | categorySlug | string | ARTICLE_CATEGORY.CATEGORY_SLUG | URL-friendly slug cho danh mục |
  540. | description | string | ARTICLE_CATEGORY.DESCRIPTION<br/>DESCRIPTION_EN<br/>DESCRIPTION_LO | Mô tả chi tiết danh mục |
  541. | iconUrl | string | ARTICLE_CATEGORY.ICON_URL | Đường dẫn icon của danh mục |
  542. | parentId | int? | ARTICLE_CATEGORY.PARENT_ID | ID danh mục cha (null = danh mục gốc) |
  543. | displayOrder | int | ARTICLE_CATEGORY.DISPLAY_ORDER | Thứ tự hiển thị (số nhỏ hơn hiển thị trước) |
  544. ### Response Fields - pagination
  545. | Field | Type | Description |
  546. |-------|------|-------------|
  547. | pageNumber | int | Trang hiện tại (bắt đầu từ 0) |
  548. | pageSize | int | Số items trên mỗi trang |
  549. | totalCount | int | Tổng số items trong database |
  550. | totalPages | int | Tổng số trang (= ceiling(totalCount / pageSize)) |
  551. ---
  552. ## 4. Article Load (List)
  553. Lấy danh sách bài viết với pagination và filters.
  554. ### Endpoint
  555. ```
  556. POST /apis/article/load
  557. ```
  558. ### Request (tất cả bài viết)
  559. ```json
  560. {
  561. "lang": "lo",
  562. "pageNumber": 0,
  563. "pageSize": 10
  564. }
  565. ```
  566. ### Request (lọc theo category)
  567. ```json
  568. {
  569. "lang": "lo",
  570. "pageNumber": 0,
  571. "pageSize": 10,
  572. "categoryId": 1,
  573. "isFeatured": false
  574. }
  575. ```
  576. | Field | Type | Default | Description |
  577. |-------|------|---------|-------------|
  578. | lang | string | "lo" | Ngôn ngữ (hoặc header Accept-Language) |
  579. | pageNumber | int | 0 | Trang hiện tại (0-indexed) |
  580. | pageSize | int | 10 | Số item mỗi trang |
  581. | categoryId | int? | **null** | **null = lấy tất cả**, có giá trị = lọc theo danh mục |
  582. | isFeatured | bool? | null | true = chỉ bài nổi bật, null = tất cả |
  583. ### Response Success
  584. ```json
  585. {
  586. "errorCode": "0",
  587. "message": "Success",
  588. "data": {
  589. "articles": [
  590. {
  591. "id": 1,
  592. "title": "Hướng dẫn cài đặt eSIM",
  593. "slug": "huong-dan-cai-dat-esim",
  594. "summary": "Tóm tắt...",
  595. "thumbnailUrl": "/images/article1.jpg",
  596. "categoryId": 1,
  597. "viewCount": 150,
  598. "isFeatured": true,
  599. "isPinned": false,
  600. "publishedDate": "2024-12-25"
  601. }
  602. ],
  603. "pagination": {
  604. "pageNumber": 0,
  605. "pageSize": 10,
  606. "totalCount": 25,
  607. "totalPages": 3
  608. }
  609. }
  610. }
  611. ```
  612. ### Response Fields - articles[]
  613. | Field | Type | DB Column | Description |
  614. |-------|------|-----------|-------------|
  615. | id | int | ARTICLE.ID | **ID bài viết** - dùng để lấy chi tiết qua `/apis/article/detail` |
  616. | title | string | ARTICLE.TITLE<br/>TITLE_EN<br/>TITLE_LO | Tiêu đề bài viết (theo ngôn ngữ) |
  617. | slug | string | ARTICLE.SLUG | URL-friendly slug (unique) |
  618. | summary | string | ARTICLE.SUMMARY<br/>SUMMARY_EN<br/>SUMMARY_LO | Tóm tắt ngắn gọn |
  619. | thumbnailUrl | string | ARTICLE.THUMBNAIL_URL | Ảnh thumbnail (dùng cho list) |
  620. | categoryId | int | ARTICLE.CATEGORY_ID | ID danh mục (FK → ARTICLE_CATEGORY) |
  621. | viewCount | int | ARTICLE.VIEW_COUNT | Số lượt xem |
  622. | isFeatured | bool | ARTICLE.IS_FEATURED | Bài viết nổi bật (true/false) |
  623. | isPinned | bool | ARTICLE.IS_PINNED | Bài viết được ghim (true/false) |
  624. | publishedDate | datetime | ARTICLE.PUBLISHED_DATE | Ngày xuất bản |
  625. ### Important Notes
  626. - **Ordering**: Danh sách sắp xếp theo:
  627. 1. `IS_PINNED` DESC (bài ghim lên đầu)
  628. 2. `PUBLISHED_DATE` DESC (mới nhất trước)
  629. 3. `CREATED_DATE` DESC
  630. - **Filter logic**:
  631. - `categoryId = null` → Lấy **tất cả** bài viết
  632. - `categoryId = 1` → Chỉ lấy bài viết thuộc category 1
  633. ### cURL Examples
  634. #### Lấy tất cả bài viết
  635. ```bash
  636. curl -X POST http://localhost:8360/apis/article/load \
  637. -H "Content-Type: application/json" \
  638. -d '{
  639. "lang": "lo",
  640. "pageNumber": 0,
  641. "pageSize": 10
  642. }'
  643. ```
  644. #### Lấy bài viết theo category
  645. ```bash
  646. curl -X POST http://localhost:8360/apis/article/load \
  647. -H "Content-Type: application/json" \
  648. -d '{
  649. "categoryId": 1,
  650. "lang": "en",
  651. "pageSize": 5
  652. }'
  653. ```
  654. #### Lấy bài viết nổi bật
  655. ```bash
  656. curl -X POST http://localhost:8360/apis/article/load \
  657. -H "Content-Type: application/json" \
  658. -d '{
  659. "isFeatured": true,
  660. "lang": "lo",
  661. "pageSize": 6
  662. }'
  663. ```
  664. ---
  665. ## 4.1 Article Detail
  666. Lấy chi tiết 1 bài viết theo ID hoặc slug.
  667. ### Endpoint
  668. ```
  669. POST /apis/article/detail
  670. ```
  671. ### Request (theo ID - khuyến nghị)
  672. ```json
  673. {
  674. "id": 1,
  675. "lang": "lo"
  676. }
  677. ```
  678. ### Request (theo slug - SEO friendly)
  679. ```json
  680. {
  681. "slug": "huong-dan-cai-dat-esim",
  682. "lang": "lo"
  683. }
  684. ```
  685. | Field | Type | Required | Description |
  686. |-------|------|----------|-------------|
  687. | id | int? | Conditional | ID bài viết (lấy từ list API) |
  688. | slug | string? | Conditional | Slug của bài viết |
  689. | lang | string | No | Ngôn ngữ (default: "lo") |
  690. **Note**: Phải có **ít nhất một** trong `id` hoặc `slug`
  691. ### Response Success
  692. ```json
  693. {
  694. "errorCode": "0",
  695. "message": "Success",
  696. "data": {
  697. "article": {
  698. "id": 1,
  699. "title": "Hướng dẫn cài đặt eSIM",
  700. "slug": "huong-dan-cai-dat-esim",
  701. "summary": "Tóm tắt...",
  702. "content": "<p>Nội dung HTML đầy đủ...</p>",
  703. "thumbnailUrl": "/images/article1.jpg",
  704. "coverImageUrl": "/images/cover1.jpg",
  705. "metaDescription": "SEO description",
  706. "metaKeywords": "esim, laos",
  707. "categoryId": 1,
  708. "viewCount": 151,
  709. "isFeatured": true,
  710. "publishedDate": "2024-12-25",
  711. "createdDate": "2024-12-20"
  712. }
  713. }
  714. }
  715. ```
  716. ### Response Fields - article
  717. | Field | Type | DB Column | Description |
  718. |-------|------|----------|-------------|
  719. | id | int | ARTICLE.ID | ID bài viết (Primary Key) |
  720. | title | string | ARTICLE.TITLE<br/>TITLE_EN<br/>TITLE_LO | Tiêu đề bài viết (theo ngôn ngữ) |
  721. | slug | string | ARTICLE.SLUG | URL-friendly slug (unique) |
  722. | summary | string | ARTICLE.SUMMARY<br/>SUMMARY_EN<br/>SUMMARY_LO | Tóm tắt ngắn gọn |
  723. | content | string | ARTICLE.CONTENT<br/>CONTENT_EN<br/>CONTENT_LO | **Nội dung HTML đầy đủ** |
  724. | thumbnailUrl | string | ARTICLE.THUMBNAIL_URL | Ảnh thumbnail |
  725. | coverImageUrl | string | ARTICLE.COVER_IMAGE_URL | Ảnh bìa (dùng cho detail page) |
  726. | metaDescription | string | ARTICLE.META_DESCRIPTION<br/>META_DESCRIPTION_EN<br/>META_DESCRIPTION_LO | SEO meta description |
  727. | metaKeywords | string | ARTICLE.META_KEYWORDS | SEO keywords (comma-separated) |
  728. | categoryId | int | ARTICLE.CATEGORY_ID | ID danh mục |
  729. | viewCount | int | ARTICLE.VIEW_COUNT | Số lượt xem (**tự động +1 khi gọi API này**) |
  730. | isFeatured | bool | ARTICLE.IS_FEATURED | Bài viết nổi bật |
  731. | publishedDate | datetime | ARTICLE.PUBLISHED_DATE | Ngày xuất bản |
  732. | createdDate | datetime | ARTICLE.CREATED_DATE | Ngày tạo bài viết |
  733. ### Important Notes
  734. - **Auto view count**: Mỗi lần gọi API này, `viewCount` tự động +1
  735. - **Recommended flow**:
  736. 1. Gọi `/apis/article/load` → Lấy `id` từ danh sách
  737. 2. Gọi `/apis/article/detail` với `id` đó
  738. ### cURL Examples
  739. #### Lấy chi tiết theo ID
  740. ```bash
  741. curl -X POST http://localhost:8360/apis/article/detail \
  742. -H "Content-Type: application/json" \
  743. -d '{
  744. "id": 1,
  745. "lang": "lo"
  746. }'
  747. ```
  748. #### Lấy chi tiết theo slug
  749. ```bash
  750. curl -X POST http://localhost:8360/apis/article/detail \
  751. -H "Content-Type: application/json" \
  752. -d '{
  753. "slug": "huong-dan-cai-dat-esim",
  754. "lang": "en"
  755. }'
  756. ```
  757. ---
  758. ## 5. Banner Load
  759. Lấy danh sách banner.
  760. ### Endpoint
  761. ```
  762. POST /apis/content/banner
  763. ```
  764. ### Request
  765. ```json
  766. {
  767. "lang": "lo",
  768. "pageNumber": 0,
  769. "pageSize": 10,
  770. "position": "home"
  771. }
  772. ```
  773. | Field | Type | Default | Description |
  774. |-------|------|---------|-------------|
  775. | lang | string | "lo" | Ngôn ngữ (hoặc header Accept-Language) |
  776. | pageNumber | int | 0 | Trang hiện tại |
  777. | pageSize | int | 10 | Số item mỗi trang |
  778. | position | string? | null | Vị trí: "home", "sidebar"... |
  779. ### Response
  780. ```json
  781. {
  782. "errorCode": "0",
  783. "data": {
  784. "banners": [
  785. {
  786. "id": 1,
  787. "title": "Banner Title",
  788. "subtitle": "Subtitle",
  789. "imageUrl": "/images/banner1.jpg",
  790. "imageMobileUrl": "/images/banner1_m.jpg",
  791. "linkUrl": "/promo",
  792. "linkTarget": "_blank",
  793. "position": "home",
  794. "displayOrder": 1
  795. }
  796. ],
  797. "pagination": {...}
  798. }
  799. }
  800. ```
  801. ### Response Fields - banners[]
  802. | Field | Type | DB Column | Description |
  803. |-------|------|-----------|-------------|
  804. | id | int | BANNER.ID | ID banner (Primary Key) |
  805. | title | string | BANNER.TITLE<br/>TITLE_EN<br/>TITLE_LO | Tiêu đề banner (theo ngôn ngữ) |
  806. | subtitle | string | BANNER.SUBTITLE<br/>SUBTITLE_EN<br/>SUBTITLE_LO | Phụ đề banner |
  807. | imageUrl | string | BANNER.IMAGE_URL | Ảnh banner (desktop) |
  808. | imageMobileUrl | string | BANNER.IMAGE_MOBILE_URL | Ảnh banner (mobile) |
  809. | linkUrl | string | BANNER.LINK_URL | URL đích khi click banner |
  810. | linkTarget | string | BANNER.LINK_TARGET | Target (_self, _blank,...) |
  811. | position | string | BANNER.POSITION | Vị trí hiển thị (home, category,...) |
  812. | displayOrder | int | BANNER.DISPLAY_ORDER | Thứ tự hiển thị |
  813. **Lọc tự động**: Chỉ trả về banners với `STATUS = 1` và trong khoảng `START_DATE ≤ now ≤ END_DATE`
  814. ---
  815. ## 6. Customer Review Load
  816. Lấy đánh giá của khách hàng.
  817. ### Endpoint
  818. ```
  819. POST /apis/content/review
  820. ```
  821. ### Request
  822. ```json
  823. {
  824. "lang": "lo",
  825. "pageNumber": 0,
  826. "pageSize": 10,
  827. "isFeatured": true
  828. }
  829. ```
  830. | Field | Type | Default | Description |
  831. |-------|------|---------|-------------|
  832. | lang | string | "lo" | Ngôn ngữ |
  833. | pageNumber | int | 0 | Trang |
  834. | pageSize | int | 10 | Số item |
  835. | isFeatured | bool? | null | Lọc review nổi bật |
  836. ### Response
  837. ```json
  838. {
  839. "errorCode": "0",
  840. "data": {
  841. "reviews": [
  842. {
  843. "id": 1,
  844. "customerName": "Nguyen Van A",
  845. "avatarUrl": "/avatars/user1.jpg",
  846. "rating": 1,
  847. "reviewContent": "Dich vu rat tot...",
  848. "destination": "Vientiane, Laos",
  849. "isFeatured": true,
  850. "createdDate": "2024-12-25"
  851. }
  852. ],
  853. "pagination": {...}
  854. }
  855. }
  856. ```
  857. ### Response Fields - reviews[]
  858. | Field | Type | DB Column | Description |
  859. |-------|------|-----------|-------------|
  860. | id | int | CUSTOMER_REVIEW.ID | ID đánh giá (Primary Key) |
  861. | customerName | string | CUSTOMER_REVIEW.CUSTOMER_NAME | Tên khách hàng |
  862. | avatarUrl | string | CUSTOMER_REVIEW.AVATAR_URL | Ảnh đại diện (nullable) |
  863. | rating | int | CUSTOMER_REVIEW.RATING | Số sao (1-5) |
  864. | reviewContent | string | CUSTOMER_REVIEW.REVIEW_CONTENT<br/>REVIEW_CONTENT_EN<br/>REVIEW_CONTENT_LO | Nội dung đánh giá (theo ngôn ngữ) |
  865. | destination | string | CUSTOMER_REVIEW.DESTINATION<br/>DESTINATION_EN<br/>DESTINATION_LO | Địa điểm du lịch |
  866. | isFeatured | bool | CUSTOMER_REVIEW.IS_FEATURED | Review nổi bật |
  867. | createdDate | datetime | CUSTOMER_REVIEW.CREATED_DATE | Ngày tạo review |
  868. **Lọc tự động**: Chỉ trả về reviews với `STATUS = 1` (đã duyệt)
  869. ---
  870. ## 6.1 Customer Review Create
  871. Khách hàng gửi đánh giá (chờ duyệt).
  872. ### Endpoint
  873. ```
  874. POST /apis/content/review/create
  875. ```
  876. ### Request
  877. ```json
  878. {
  879. "lang": "lo",
  880. "customerName": "Nguyen Van A",
  881. "reviewContent": "Dich vu rat tot, toi rat hai long!",
  882. "destination": "Vientiane, Laos",
  883. "rating": 5
  884. }
  885. ```
  886. | Field | Type | Required | Description |
  887. |-------|------|----------|-------------|
  888. | lang | string | No | Ngôn ngữ |
  889. | customerName | string | Yes | Tên khách hàng |
  890. | reviewContent | string | Yes | Nội dung đánh giá |
  891. | destination | string | No | Địa điểm |
  892. | rating | int | No | Đánh giá (1-5) |
  893. ### Response Success
  894. ```json
  895. {
  896. "errorCode": "0",
  897. "message": "Review submitted successfully",
  898. "data": {
  899. "reviewId": 123
  900. }
  901. }
  902. ```
  903. ### Note
  904. - Review mới sẽ có `Status = false` (chờ admin duyệt)
  905. ---
  906. ## 7. FAQ Category Load
  907. Lấy danh mục FAQ (hỗ trợ cấu trúc phân cấp).
  908. ### Endpoint
  909. ```
  910. POST /apis/content/faq-category
  911. ```
  912. ### Request
  913. ```json
  914. {
  915. "lang": "lo",
  916. "pageNumber": 0,
  917. "pageSize": 10,
  918. "parentId": null
  919. }
  920. ```
  921. | Field | Type | Default | Description |
  922. |-------|------|---------|-------------|
  923. | lang | string | "lo" | Ngôn ngữ (hoặc header Accept-Language) |
  924. | pageNumber | int | 0 | Trang hiện tại |
  925. | pageSize | int | 10 | Số item mỗi trang |
  926. | parentId | int? | null | ID danh mục cha (null = lấy root categories) |
  927. ### Response
  928. ```json
  929. {
  930. "errorCode": "0",
  931. "data": {
  932. "categories": [
  933. {
  934. "id": 1,
  935. "categoryName": "Cài đặt eSIM",
  936. "categorySlug": "cai-dat-esim",
  937. "description": "Hướng dẫn cài đặt",
  938. "iconUrl": "/icons/setup.png",
  939. "parentId": null,
  940. "displayOrder": 1
  941. }
  942. ],
  943. "pagination": {...}
  944. }
  945. }
  946. ```
  947. ### Response Fields - categories[]
  948. | Field | Type | DB Column | Description |
  949. |-------|------|-----------|-------------|
  950. | id | int | FAQ_CATEGORY.ID | ID danh mục FAQ (Primary Key) |
  951. | categoryName | string | FAQ_CATEGORY.CATEGORY_NAME<br/>CATEGORY_NAME_EN<br/>CATEGORY_NAME_LO | Tên danh mục FAQ |
  952. | categorySlug | string | FAQ_CATEGORY.CATEGORY_SLUG | URL-friendly slug |
  953. | description | string | FAQ_CATEGORY.DESCRIPTION<br/>DESCRIPTION_EN<br/>DESCRIPTION_LO | Mô tả danh mục |
  954. | iconUrl | string | FAQ_CATEGORY.ICON_URL | Icon của danh mục |
  955. | parentId | int? | FAQ_CATEGORY.PARENT_ID | **ID danh mục cha (null = danh mục gốc)** |
  956. | displayOrder | int | FAQ_CATEGORY.DISPLAY_ORDER | Thứ tự hiển thị |
  957. ### Use Cases
  958. - **Homepage FAQ**: `parentId = null` → Lấy danh mục gốc
  959. - **Support Center**: `parentId = <categoryId>` → Lấy danh mục con
  960. - Hỗ trợ cấu trúc phân cấp không giới hạn cấp độ
  961. ---
  962. ## 8. FAQ Load
  963. Lấy danh sách câu hỏi thường gặp.
  964. ### Endpoint
  965. ```
  966. POST /apis/content/faq
  967. ```
  968. ### Request
  969. ```json
  970. {
  971. "lang": "lo",
  972. "pageNumber": 0,
  973. "pageSize": 10,
  974. "categoryId": 1,
  975. "isFeatured": false
  976. }
  977. ```
  978. | Field | Type | Default | Description |
  979. |-------|------|---------|-------------|
  980. | lang | string | "lo" | Ngôn ngữ |
  981. | pageNumber | int | 0 | Trang |
  982. | pageSize | int | 10 | Số item |
  983. | categoryId | int? | null | Lọc theo danh mục |
  984. | isFeatured | bool? | null | Lọc FAQ nổi bật |
  985. ### Response
  986. ```json
  987. {
  988. "errorCode": "0",
  989. "data": {
  990. "faqs": [
  991. {
  992. "id": 1,
  993. "question": "Làm sao để cài đặt eSIM?",
  994. "answer": "<p>Hướng dẫn chi tiết...</p>",
  995. "categoryId": 1,
  996. "viewCount": 100,
  997. "isFeatured": true
  998. }
  999. ],
  1000. "pagination": {...}
  1001. }
  1002. }
  1003. ```
  1004. ### Response Fields - faqs[]
  1005. | Field | Type | DB Column | Description |
  1006. |-------|------|-----------|-------------|
  1007. | id | int | FAQ.ID | ID câu hỏi (Primary Key) |
  1008. | question | string | FAQ.QUESTION<br/>QUESTION_EN<br/>QUESTION_LO | Câu hỏi (theo ngôn ngữ) |
  1009. | answer | string | FAQ.ANSWER<br/>ANSWER_EN<br/>ANSWER_LO | Câu trả lời (HTML format) |
  1010. | categoryId | int | FAQ.CATEGORY_ID | ID danh mục (FK → FAQ_CATEGORY) |
  1011. | viewCount | int | FAQ.VIEW_COUNT | Số lượt xem |
  1012. | isFeatured | bool | FAQ.IS_FEATURED | FAQ nổi bật |
  1013. **Lọc tự động**: Chỉ trả về FAQs với `STATUS = 1`
  1014. ---
  1015. ## 9. Device eSIM Compatibility - Get Metadata
  1016. Lấy danh sách brands và categories để hiển thị tabs/filters cho tính năng kiểm tra thiết bị.
  1017. ### Endpoint
  1018. ```
  1019. GET /apis/content/device-metadata
  1020. ```
  1021. ### Request
  1022. Không cần body (GET request)
  1023. ### Response
  1024. ```json
  1025. {
  1026. "errorCode": "0",
  1027. "message": "Success",
  1028. "data": {
  1029. "brands": [
  1030. {
  1031. "brand": "Apple",
  1032. "deviceCount": 24,
  1033. "popularCount": 24,
  1034. "devices": [
  1035. {
  1036. "id": 1,
  1037. "modelName": "iPhone XS",
  1038. "category": "Phone",
  1039. "isPopular": true,
  1040. "displayOrder": 1,
  1041. "modelNameEn": "iPhone XS",
  1042. "modelNameLo": "iPhone XS",
  1043. "notes": "Tất cả phiên bản trừ China Mainland, Hong Kong, Macao",
  1044. "notesEn": "All versions except China Mainland, Hong Kong, Macao",
  1045. "notesLo": "ທຸກເວີຊັນຍົກເວັ້ນ China Mainland, Hong Kong, Macao"
  1046. }
  1047. ]
  1048. },
  1049. {
  1050. "brand": "Samsung",
  1051. "deviceCount": 19,
  1052. "popularCount": 19,
  1053. "devices": [...]
  1054. }
  1055. ],
  1056. "categories": [
  1057. {
  1058. "category": "Phone",
  1059. "deviceCount": 67
  1060. },
  1061. {
  1062. "category": "Tablet",
  1063. "deviceCount": 4
  1064. }
  1065. ]
  1066. }
  1067. }
  1068. ```
  1069. ### Response Fields - brands[]
  1070. | Field | Type | Description |
  1071. |-------|------|-------------|
  1072. | brand | string | Tên hãng (Apple, Samsung, Google,...) |
  1073. | deviceCount | int | Tổng số thiết bị của hãng này |
  1074. | popularCount | int | Số thiết bị phổ biến (IS_POPULAR = 1) |
  1075. | **devices[]** | array | **Danh sách tất cả thiết bị của brand** |
  1076. ### Response Fields - brands[].devices[]
  1077. | Field | Type | Description |
  1078. |-------|------|-------------|
  1079. | id | int | ID thiết bị |
  1080. | modelName | string | Tên model (mặc định Vietnamese) |
  1081. | category | string | Loại thiết bị (Phone, Tablet, Laptop, Watch) |
  1082. | isPopular | bool | Thiết bị phổ biến |
  1083. | displayOrder | int | Thứ tự sắp xếp |
  1084. | modelNameEn | string | Tên model tiếng Anh |
  1085. | modelNameLo | string | Tên model tiếng Lào |
  1086. | notes | string | Ghi chú (Vietnamese) |
  1087. | notesEn | string | Ghi chú (English) |
  1088. | notesLo | string | Ghi chú (Lao) |
  1089. ### Response Fields - categories[]
  1090. | Field | Type | Description |
  1091. |-------|------|-------------|
  1092. | category | string | Loại thiết bị (Phone, Tablet, Laptop, Watch) |
  1093. | deviceCount | int | Tổng số thiết bị thuộc category này |
  1094. ### Use Case
  1095. API này trả về **TẤT CẢ** devices trong một lần gọi để:
  1096. - Frontend có thể filter/search ở client-side
  1097. - Hiển thị tabs cho từng brand với danh sách devices
  1098. - Không cần pagination vì dùng cho client-side filtering
  1099. - Giảm số lượng API calls
  1100. ### Important Notes
  1101. - **Trả về toàn bộ devices** trong response (không phân trang)
  1102. - Frontend tự filter theo brand/category/search
  1103. - Chỉ devices có `STATUS = 1` và `SUPPORTS_ESIM = 1`
  1104. - Sắp xếp theo `DISPLAY_ORDER`, `BRAND`, `MODEL_NAME`
  1105. ---
  1106. ## 10. Device eSIM Compatibility - Search & Filter
  1107. Tìm kiếm và lọc danh sách thiết bị hỗ trợ eSIM.
  1108. ### Endpoint
  1109. ```
  1110. POST /apis/content/device-compatibility
  1111. ```
  1112. ### Request Examples
  1113. #### Example 1: Lấy tất cả devices phổ biến (Quick Lookup)
  1114. ```json
  1115. {
  1116. "isPopular": true,
  1117. "lang": "lo",
  1118. "pageNumber": 0,
  1119. "pageSize": 50
  1120. }
  1121. ```
  1122. #### Example 2: Filter theo brand (Tab Apple)
  1123. ```json
  1124. {
  1125. "brand": "Apple",
  1126. "isPopular": true,
  1127. "lang": "en",
  1128. "pageNumber": 0,
  1129. "pageSize": 50
  1130. }
  1131. ```
  1132. #### Example 3: Search keyword
  1133. ```json
  1134. {
  1135. "searchKeyword": "iPhone 13",
  1136. "lang": "lo",
  1137. "pageNumber": 0,
  1138. "pageSize": 20
  1139. }
  1140. ```
  1141. #### Example 4: Filter brand + category
  1142. ```json
  1143. {
  1144. "brand": "Apple",
  1145. "category": "Tablet",
  1146. "lang": "en",
  1147. "pageNumber": 0,
  1148. "pageSize": 10
  1149. }
  1150. ```
  1151. ### Request Parameters
  1152. | Field | Type | Required | Default | Description |
  1153. |-------|------|----------|---------|-------------|
  1154. | brand | string | No | null | Filter theo hãng (Apple, Samsung, Google,...) |
  1155. | category | string | No | null | Filter theo loại (Phone, Tablet, Laptop, Watch) |
  1156. | searchKeyword | string | No | null | Tìm kiếm trong tên model (all languages) |
  1157. | isPopular | bool | No | null | `true` = chỉ lấy thiết bị phổ biến (quick lookup) |
  1158. | lang | string | No | "lo" | Ngôn ngữ response: "lo", "en" |
  1159. | pageNumber | int | No | 0 | Trang hiện tại (0-indexed) |
  1160. | pageSize | int | No | 50 | Số items/page (mặc định 50 cho device list) |
  1161. ### Response Success
  1162. ```json
  1163. {
  1164. "errorCode": "0",
  1165. "message": "Success",
  1166. "data": {
  1167. "devices": [
  1168. {
  1169. "id": 1,
  1170. "brand": "Apple",
  1171. "modelName": "iPhone XS",
  1172. "category": "Phone",
  1173. "notes": "ທຸກເວີຊັນຍົກເວັ້ນ iPhone ຈາກ ຈີນແຜ່ນດິນໃຫຍ່, ຮ່ອງກົງ ແລະ ມາເກົາ",
  1174. "supportsEsim": true,
  1175. "isPopular": true,
  1176. "displayOrder": 1
  1177. },
  1178. {
  1179. "id": 2,
  1180. "brand": "Apple",
  1181. "modelName": "iPhone 13",
  1182. "category": "Phone",
  1183. "notes": null,
  1184. "supportsEsim": true,
  1185. "isPopular": true,
  1186. "displayOrder": 12
  1187. }
  1188. ],
  1189. "pagination": {
  1190. "pageNumber": 0,
  1191. "pageSize": 50,
  1192. "totalCount": 24,
  1193. "totalPages": 1
  1194. }
  1195. }
  1196. }
  1197. ```
  1198. ### Response Fields - devices[]
  1199. | Field | Type | DB Column | Description |
  1200. |-------|------|-----------|-------------|
  1201. | id | int | DEVICE_ESIM_COMPATIBILITY.ID | ID thiết bị (Primary Key) |
  1202. | brand | string | DEVICE_ESIM_COMPATIBILITY.BRAND | Hãng sản xuất (Apple, Samsung,...) |
  1203. | 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) |
  1204. | category | string | DEVICE_ESIM_COMPATIBILITY.CATEGORY | Loại thiết bị (Phone, Tablet, Laptop, Watch) |
  1205. | notes | string | DEVICE_ESIM_COMPATIBILITY.NOTES<br/>NOTES_EN<br/>NOTES_LO | Lưu ý đặc biệt (nullable, theo ngôn ngữ) |
  1206. | supportsEsim | bool | DEVICE_ESIM_COMPATIBILITY.SUPPORTS_ESIM | Hỗ trợ eSIM (luôn = true cho kết quả được trả về) |
  1207. | isPopular | bool | DEVICE_ESIM_COMPATIBILITY.IS_POPULAR | Thiết bị phổ biến (hiển thị trong quick lookup) |
  1208. | displayOrder | int | DEVICE_ESIM_COMPATIBILITY.DISPLAY_ORDER | Thứ tự sắp xếp |
  1209. ### Filter Logic
  1210. - **Auto-filter**: Chỉ trả về devices với `STATUS = 1` và `SUPPORTS_ESIM = 1`
  1211. - **Search**: Tìm kiếm keyword trong `MODEL_NAME`, `MODEL_NAME_EN`, `MODEL_NAME_LO` (case-insensitive)
  1212. - **Ordering**: `DISPLAY_ORDER` ASC → `BRAND` ASC → `MODEL_NAME` ASC
  1213. ### cURL Examples
  1214. #### Get metadata (brands & categories)
  1215. ```bash
  1216. curl -X GET http://localhost:8360/apis/content/device-metadata \
  1217. -H "Content-Type: application/json"
  1218. ```
  1219. #### Search for "iPhone 13"
  1220. ```bash
  1221. curl -X POST http://localhost:8360/apis/content/device-compatibility \
  1222. -H "Content-Type: application/json" \
  1223. -d '{
  1224. "searchKeyword": "iPhone 13",
  1225. "lang": "en",
  1226. "pageSize": 20
  1227. }'
  1228. ```
  1229. #### Get popular Apple devices
  1230. ```bash
  1231. curl -X POST http://localhost:8360/apis/content/device-compatibility \
  1232. -H "Content-Type: application/json" \
  1233. -d '{
  1234. "brand": "Apple",
  1235. "isPopular": true,
  1236. "lang": "lo",
  1237. "pageSize": 50
  1238. }'
  1239. ```
  1240. ### Implementation Notes
  1241. - **Sample Data**: 70+ devices đã có sẵn trong `Database/DeviceCompatibility_Schema.sql`
  1242. - **Quick Lookup**: Set `isPopular=true` để chỉ lấy thiết bị phổ biến (Apple 24 devices, Samsung 19 devices, Google Pixel 10 devices)
  1243. - **Full Search**: Không set `isPopular` và dùng `searchKeyword` để tìm kiếm toàn bộ database
  1244. - **Multi-language**: Notes field hỗ trợ 3 ngôn ngữ (vi, en, lo)
  1245. ---