api.flyer.usBức tranh hiện trạng: ứng dụng nào gọi dịch vụ nào, bốn thế hệ API đang chạy song song, cách đội quản lý cơ sở dữ liệu, triển khai và giám sát.
1. em check và vẽ được diagram database hiện tại của FLYER không? 2. System design structure + api show cách hiện tại team đang quản lý nữa
Tài liệu do trợ lý AI soạn trong một phiên làm việc, bằng cách đọc bản sao 37 repo (cập nhật cuối tháng 8/2026) và các tệp đặc tả API tải ngày 06/09/2026. Đây là bản mô tả hiện trạng để thảo luận, chưa phải kết luận của đội kỹ thuật.
Nguồn: bản sao 37 repo Gitea flyer/*, trong đó các tệp chính là exam/main.go (bảng định tuyến), exam/deploy/lib/app.libsonnet, flyer-rs/deploy/lib/app.libsonnet, flyer-rs/shared/src/jwt.rs, flyer-go/AGENTS.md (quy ước đánh số phiên bản API và migration), partner-web/next.config.js (rewrites), backend-v1/packages/hasura/deploy/environments/production/env.yaml; đặc tả API gồm OpenAPI v3, v4 teacher và hai schema GraphQL (v2, Hasura) tải từ api.flyer.us và staging-api.flyer.us.
Giới hạn:
api.flyer.us không nằm trong repo nào (ingress chủ yếu bị comment). Nguồn dựng lại: rewrites của partner-web/next.config.js và ingress của admin-web, admin-v1.customer-success chạy trên Supabase riêng; PowerBI đẩy dữ liệu Postgres sang Parquet trên S3) và các nguyên mẫu (elevenlabs, test-realtime-call, python STT/TTS chưa có cấu hình k8s).api.flyer.usMọi dịch vụ chạy trên một cụm EKS tên flyer (ap-southeast-1) và dùng chung một Postgres trên RDS. Ứng dụng web Next.js của giáo viên và CRM tự chuyển tiếp request sang dịch vụ trong cụm qua rewrites, nên trong thực tế có hai lối vào: cổng api.flyer.us và từng web tự làm cổng.
luồng request chính gọi phụ, không đồng bộ, hoặc hệ thống cũ
| Dịch vụ (repo) | Công nghệ | Vai trò | Lưu trữ riêng | Triển khai |
|---|---|---|---|---|
exam-v2 (exam) | Go, gqlgen, ent, Huma | Lõi phía học sinh: thi, học, chấm nói/viết bằng AI, gói cước, thiết bị Bingo | Redis, S3, Temporal, NATS, Qdrant; gọi Hasura bằng quyền admin | Tanka |
flyer-backend-rust (flyer-rs) | Rust, Axum, sqlx, Restate | CRM, giáo viên, phụ huynh, xác thực, thanh toán SePay | ReadySet cache, Restate; tự co giãn 2–10 pod | Tanka |
teacher-api (flyer-go) | Go, Huma, sqlc, Temporal | Khung "base" + một dịch vụ mỗi nhánh (dev/teacher, dev/student, dev/admin); production mới chạy teacher | Redis, S3 | Tanka, migration chạy trong CI |
Hasura (backend-v1) | Hasura v2.40.2 + actions NestJS | GraphQL tự sinh cho web/app cũ, xác thực qua webhook | — | Kustomize |
| upload | Go, Echo, gRPC | Tải và xử lý ảnh, âm thanh, bản ghi âm | S3 | Kustomize |
partner-backend (backend-nodejs) | NestJS, TypeORM, Bull | Lớp, giao bài, lịch cho trường (thế hệ 2022) | Redis (hàng đợi) | Kustomize |
rest-api (backend-nodejs) | NestJS | Cầu nối VNPay và SACE (iService) | — | Chỉ có Dockerfile, không có CI |
Hai điểm dễ nhầm: backend-nodejs/apps/backend-nodejs là khung NestJS rỗng, GraphQL v2 thật do exam (Go) phục vụ; repo speaking là web Next.js, không phải backend chấm nói.
| Ứng dụng | Tên miền | Hasura | v2 GraphQL | v3 | v4 | upload |
|---|---|---|---|---|---|---|
| partner-web | teacher.flyer.us | có | có | có | có | |
| center-crm-frontend | crm.flyer.us | có | có | có | ||
| admin-web | admin.flyer.vn | có | có | có | có | |
| admin-v1 | admin-old.flyer.vn | có | có | có | ||
| webs-exam | exam.flyer.us | có | có | có | ||
| webs-ielts | ielts.flyer.us | có | có | |||
| webs-ranking | ranking.flyer.us | có | có | có | ||
| webs-study, webs-signup | study, signup.flyer.us | có | ||||
| exam-web (cũ) | exam-old.flyer.us | có | ||||
| mobile-student | App "FLYER" | có | có | |||
| mobile-partner | App "FLYER Teacher" | có | có | |||
| mobile (cũ) | com.flyer.exam | có | có | có | ||
| mobile-ielts, mobile-study | IELTS, Bingo AI | có |
GraphQL v2 là API được dùng rộng nhất (12/13 nhóm ứng dụng). Hasura vẫn nằm trong 8 nhóm, dù phần lớn web mới chỉ còn dùng nó cho subscription qua websocket.
Quy ước đánh số: mỗi dịch vụ giữ một tiền tố phiên bản, dịch vụ mới lấy số trống kế tiếp. Số phiên bản vì vậy chỉ dịch vụ nào sở hữu, không phải bản nâng cấp của cùng một API.
| Tiền tố | Dịch vụ | Kiểu | Định nghĩa bằng | Quy mô | Tài liệu |
|---|---|---|---|---|---|
| /graphql | Hasura | GraphQL tự sinh | Theo dõi bảng Postgres | 157 query · 61 mutation · 154 subscription | Introspection |
| /v1, /v2 | exam-v2 | GraphQL + REST | Schema-first (gqlgen); REST bằng Huma | 350 query · 456 mutation · 5 subscription | Apollo Sandbox tại /v2 |
| /v3 | flyer-backend-rust | REST | Code-first (aide + schemars) | 182 path · 242 thao tác | /v3/openapi.json, /v3/docs |
| /v4/<dịch vụ> | teacher-api | REST | Code-first (Huma) | 29 path · 40 thao tác | /v4/teacher/openapi, chỉ staging |
| /api, /rest | partner, rest-api | REST | NestJS | không có đặc tả | Không công bố |
v3 theo nhóm: CRM 101 path, Teacher 60, Learner 10, Parent 5, Auth 2, còn lại 1 mỗi nhóm. v4 teacher: đá trí tuệ 11, cửa hàng quà 8, điểm danh 6, trường 3, thư viện 1.
| Đường dẫn | Tên dịch vụ trong cụm |
|---|---|
| /graphql | hasura:4000/v1/graphql |
| /v2/graphql, /v2/rest/*, /rest/v1, /v2/verification | exam-v2:4000 |
| /v3/* | flyer-backend-rust:3000 |
| /v4/* | teacher-api:8081 |
| /image, /audio, /record | upload:4000 |
| /api | partner-backend |
| Lớp | Cơ chế |
|---|---|
| v3 (Rust) | JWT ký ES256, issuer https://flyer.us, có claim role (vd teacher); header Authorization |
| v2 và v4 (Go) | Dùng chung định dạng token, cookie và bảng users/user_sessions với v3. Quyền theo vai trò trong trường, cộng vai trò quản trị MANAGER / ADMIN / SUPER_MANAGER |
| Hasura | Không dùng JWT mà gọi webhook actions:5000/auth mỗi request; webhook trả x-hasura-role (user, staged, …) và mã người dùng, hồ sơ |
| Giữa các dịch vụ | Header X_SERVICE_TOKEN; exam và rest-api gọi Hasura bằng quyền admin |
staging-api.flyer.us/v2/graphql, rồi gọi qua Apollo Client.@hey-api/openapi-ts, gọi qua openapi-fetch. App di động giữ bản sao đặc tả trong repo (mobile-student 194 path, mobile-partner 182 path) nên phải chạy lại bước sinh mã khi backend đổi.{data, pagination}) thống nhất giữa v3 và v4 theo hợp đồng của flyer-rs, để một bộ phân trang phía web dùng được cho cả hai.Một Postgres, bảy cách thay đổi cấu trúc. Không có nơi nào giữ toàn bộ lịch sử: repo database rỗng (một commit 04/2025 chỉ có thư mục .idea), metadata Hasura không nằm trong Git.
| Dịch vụ | Công cụ | Số migration | Ghi chú |
|---|---|---|---|
| flyer-rs | sqlx migrate | 87 | Mới nhất 0087, 20/08/2026 |
| flyer-go | golang-migrate | 99 | 0001–0085 chép nguyên từ flyer-rs (bỏ 0033), từ 0086 đánh số riêng, cao nhất 0100 |
| exam | ent + Atlas | 10 | 09/2023 → 08/2026 |
| upload | sqlc + migrate | 8 | |
| partner (NestJS) | TypeORM | 8 | Dừng từ 2022 |
| Hasura (backend-v1) | SQL viết tay | — | view, trigger, hàm, chỉ mục; commit cuối 07/2024 |
| customer-success | Supabase | 53 | CSDL riêng, không phải Postgres chính |
flyer-go cố ý giữ 0001–0085 trùng với flyer-rs để so bằng diff. Từ 0086 hai bên tách: flyer-rs 0086_users_pending_email_phone trong khi flyer-go 0086_bingo_subjects_add_source_columns; cùng một thay đổi rename_duolingo_to_bingo_study mang số 0087 bên Rust và 0094 bên Go. Phép so diff không còn phát hiện được lệch, và không có cơ chế nào ngăn hai dịch vụ cùng sửa một bảng.
Quy ước sở hữu bảng duy nhất đang có: bảng do flyer-go tạo mang tiền tố tên dịch vụ (teacher_class_attendance, teacher_wisdom_stones). Bảng của flyer-rs, exam và Hasura không có tiền tố, nên không biết dịch vụ nào có quyền xoá.
flyer/*flyerproduction · developmentstaging-api.flyer.us) chính là namespace development.backend-secrets.| Công cụ | Dùng ở đâu |
|---|---|
Sentry tự host (sentry.flyer.us) | webs-exam, webs-ielts, webs-ranking |
| Datadog RUM | Chỉ partner-web |
| Mixpanel | webs-exam, webs-signup |
| Mattermost | Thông báo build/deploy; exam gửi cảnh báo lỗi |
| Log JSON có tracing | flyer-rs |
Không tìm thấy công cụ đo hiệu năng phía máy chủ (APM) cho dịch vụ nào. Khi một API chậm, đội chỉ thấy triệu chứng ở trình duyệt (Datadog RUM của partner-web) hoặc log.
Chín vấn đề, xếp theo mức ảnh hưởng.
| # | Vấn đề | Mức | Hướng xử lý |
|---|---|---|---|
| 1 | Khoá quản trị cơ sở dữ liệu có nguy cơ lộ phía trình duyệt ở một công cụ nội bộ | Nghiêm trọng | Bỏ khoá khỏi mã phía trình duyệt, xoay khoá |
| 2 | Cơ chế xác thực của một API có thể chấp nhận khoá ký cũ đang nằm trong repo | Nghiêm trọng | Thu hồi khoá cũ, kiểm tra hạn khoá khi xác thực |
| 3 | Một số khoá dịch vụ được commit trong repo và đóng vào image | Cao | Xoay khoá, chuyển vào secret của cụm |
| 4 | Cấu hình GraphQL tự sinh mở rộng hơn mức cần | Cao | Siết cấu hình production |
| 5 | Khoá của CI đặt ở biến thường thay vì biến bí mật | Trung bình | Chuyển sang biến bí mật |
| 6 | Hai sổ migration lệch số trên cùng một CSDL | Trung bình | Chốt một sổ duy nhất |
| 7 | Cấu hình đang chạy không có trong Git | Trung bình | Xuất metadata Hasura và ingress vào repo |
| 8 | Bốn thế hệ API và các repo cũ không còn bảo trì | Thấp, tích luỹ | Chốt API đích cho tính năng mới |
| 9 | Không có giám sát phía máy chủ | Thấp, tích luỹ | Bật tracing cho exam, flyer-rs, flyer-go |
Các mục này cùng xoay quanh một khoá quản trị có quyền đọc ghi toàn bộ dữ liệu. Chỉ xoay khoá thì bản build kế tiếp có thể lộ lại. Chỉ sửa mã mà không xoay khoá thì khoá cũ vẫn dùng được. Hai việc phải đi cùng một đợt.
| Số | flyer-rs (sqlx) | flyer-go (golang-migrate) |
|---|---|---|
| 0001–0085 | Trùng nhau, trừ 0033 (flyer-go có chỉnh sửa riêng) | |
| 0086 | users_pending_email_phone | bingo_subjects_add_source_columns |
| 0087 | rename_duolingo_to_bingo_study | teacher_wisdom_stones_t |
| 0094 | — | rename_duolingo_to_bingo_study (cùng thay đổi với 0087 bên Rust) |
| 0088–0100 | — | 13 migration riêng của flyer-go |
Hệ quả: không ai biết chắc CSDL production đang ở trạng thái của sổ nào. Một thay đổi có thể chạy hai lần, hoặc một bên thiếu cột mà bên kia đã thêm. Phép so diff mà flyer-go dựa vào để phát hiện lệch đã mất tác dụng từ 0086. Xử lý: chọn một sổ là nguồn duy nhất (flyer-go đã có quy trình rõ trong AGENTS.md), bên còn lại chỉ chép sang; thêm bước CI báo lỗi khi hai sổ khác nhau.
| Thành phần | Tình trạng trong repo | Rủi ro |
|---|---|---|
| Metadata Hasura (bảng được theo dõi, quan hệ, phân quyền theo vai trò) | Không có ở repo nào; repo database rỗng | Không xem lại được ai sửa quyền gì; mất cụm là mất toàn bộ phân quyền |
Định tuyến api.flyer.us | Ingress chủ yếu bị comment | Không biết chắc đường dẫn nào vào dịch vụ nào |
| Hasura và actions | backend-v1 commit cuối 10/07/2024 | Dịch vụ xác thực cho Hasura đang chạy bản không ai sửa trong hơn 2 năm |
Xử lý: chạy hasura metadata export và kubectl get ingress -o yaml, commit vào repo database (hoặc repo hạ tầng), từ đó thay đổi chỉ đi qua pull request.
| Hạng mục | Hiện trạng |
|---|---|
| API song song | Hasura, v2 (Go), v3 (Rust), v4 (Go) cùng phục vụ; partner-web gọi cả bốn. Năm ngôn ngữ backend: Go, Rust, TypeScript, Python, SQL |
| Web cũ | report và ranking-web dùng Next 11.1.0 (bản 2021), admin-v1 dùng React 16; commit cuối 09–10/2024 và 03/2025 |
| Không có CI | 10 repo, trong đó backend-v1 (Hasura) và rest-api (VNPay) vẫn chạy production |
Xử lý: chốt quy tắc "tính năng mới chỉ viết ở v3 hoặc v4"; lập danh sách đóng băng cho Hasura và NestJS, kèm mốc gỡ admin-old và exam-old.
Không dịch vụ backend nào có APM hay tracing phân tán. Sentry chỉ gắn ở 3 web (exam, ielts, ranking), Datadog chỉ đo trình duyệt ở partner-web. Khi API chậm hoặc lỗi, đội biết qua người dùng hoặc log, không có số liệu độ trễ theo đường dẫn. Xử lý: bật OpenTelemetry cho exam, flyer-rs, flyer-go, đẩy về Sentry sẵn có (sentry.flyer.us hỗ trợ tracing) để không phát sinh chi phí công cụ mới.
| Khi nào | Việc | Mục |
|---|---|---|
| Trong 24 giờ | Xác minh hai vấn đề nghiêm trọng; nếu đúng thì xoay khoá và thu hồi khoá ký cũ | 1, 2 |
| Trong tuần | Sửa mã gây lộ khoá; xoay các khoá còn lại; siết cấu hình; chuyển khoá CI sang biến bí mật | 1, 3, 4, 5 |
| Trong tháng | Gộp sổ migration; đưa metadata Hasura và ingress vào Git | 6, 7 |
| Trong quý | Quy tắc API đích, lộ trình gỡ repo cũ; tracing backend | 8, 9 |
Tài liệu liên quan: Bản đồ tên miền · Bộ sự kiện đo lường · Danh mục PRD · Roadmap