EA GoldenRobberGoldenRobber EA
Khôi phục TMap khi MT5 sậpTMap disaster recovery
Cách GoldenRobber lưu Ticket Map ra file và lên cloud, để một MT5 khác (máy khác, cùng magic và account) nhận lại đúng round của từng position sau khi máy chính sập. How GoldenRobber stores its Ticket Map locally and in the cloud, so that another MT5 (different machine, same magic and account) can recover the round of every position after the main machine crashes.
- Production chỉ dùng local file + fxconsole MySQL.
InpCloudCfDomainđể trống trong config ổn định, nên Cloudflare Worker + D1 không được dùng. - EA gọi endpoint theo từng EA:
/easupported/{slug}/api/tmap/{save,load,clear,heartbeat}, xác thực bằng bearer riêng của product (easupported.secret_token).InpCloudMyApiDomain=https://fxconsole.gamelau.uk/easupported/goldenrobber. - Từ fxconsole
2026.09.04.1,savebỏ qua ghi DB khi map không đổi (fingerprint trong Redis). - Các phần đánh dấu LEGACY (secret toàn cục
TMAP_SECRET_KEY, endpoint/api/tmap/*, inputInpCloudSecretKey/InpCloudCfUrl/InpCloudMyApiUrl) chỉ giữ để tham khảo.
- Production uses only the local file + fxconsole MySQL.
InpCloudCfDomainis empty in the stable config, so the Cloudflare Worker + D1 is not used. - The EA calls per-EA endpoints:
/easupported/{slug}/api/tmap/{save,load,clear,heartbeat}, authenticated with the product's own bearer (easupported.secret_token).InpCloudMyApiDomain=https://fxconsole.gamelau.uk/easupported/goldenrobber. - Since fxconsole
2026.09.04.1,saveskips the DB write when the map is unchanged (fingerprint in Redis). - Parts marked LEGACY (global secret
TMAP_SECRET_KEY,/api/tmap/*endpoints, inputsInpCloudSecretKey/InpCloudCfUrl/InpCloudMyApiUrl) are kept for reference only.
Vấn đề và giải phápProblem and solution
Vấn đề: EA GoldenRobber dùng Ticket Mapping để track partial close (position đổi ticket sau khi đóng partial trên tài khoản hedging, làm mất comment gốc). Nếu MT5 sập và mở instance khác ở máy khác, mapping bị mất → EA không biết position thuộc round nào → logic Tia/SingleRoundMode sai.
Giải pháp: lưu TMap qua 3 phương thức song song:
Problem: the GoldenRobber EA uses Ticket Mapping to track partial closes (on a hedging account a position gets a new ticket after a partial close and loses its original comment). If MT5 crashes and another instance is opened on a different machine, the mapping is lost → the EA no longer knows which round a position belongs to → Tia/SingleRoundMode logic goes wrong.
Solution: store the TMap through 3 parallel methods:
| # | Phương thứcMethod | Khi nào hoạt độngWhen it runs | Ưu điểmAdvantage | ProductionProduction |
|---|---|---|---|---|
| 1 | Local file GR_TicketMap_<magic>.csv trong thư mục dữ liệu chung của terminal (FILE_COMMON)Local file GR_TicketMap_<magic>.csv in the terminals' common data folder (FILE_COMMON) |
Luôn (cả backtest)Always (backtests too) | Nhanh nhất; lưu trữ chínhFastest; primary storage | đang dùngin use |
| 2 | Cloudflare Worker + D1 | Chỉ live, khi InpCloudCfDomain có giá trịLive only, when InpCloudCfDomain is set |
HA toàn cầu, free tier 100k req/ngàyGlobal HA, free tier 100k req/day | không dùngnot used |
| 3 | fxconsole REST API → MySQL | Chỉ liveLive only | Dự phòng nếu Cloudflare sập; tự hostBackup if Cloudflare is down; self-hosted | đang dùngin use |
Định danh mỗi instanceIdentifying each instance
Key duy nhất trên cloud = magic + account. Mở MT5 ở máy khác với cùng config (cùng magic, cùng account) thì EA tự load TMap từ cloud. File local chỉ đặt tên theo magic, vì nó nằm trên máy của chính terminal đó.
The unique key in the cloud = magic + account. Opening MT5 on another machine with the same config (same magic, same account) makes the EA load the TMap from the cloud automatically. The local file is named by magic only, since it lives on that terminal's own machine.
Kiến trúcArchitecture
flowchart LR
A["MT5 instance A (chính)<br/>GoldenRobber"]
F[("Local file<br/>GR_TicketMap_magic.csv")]
CF[("Cloudflare Worker → D1<br/>/tmap/save")]
MY[("fxconsole → MySQL<br/>/easupported/goldenrobber/api/tmap/save")]
A -- "luôn" --> F
A -. "live, chỉ khi InpCloudCfDomain có giá trị" .-> CF
A -- "live, chỉ leader" --> MYLưu: file local mỗi lần gọi; cloud theo chu kỳ InpCloudSyncIntervalSec và chỉ khi node là leader. Nét đứt = không dùng trong production.
flowchart TB
S(["OnInit trên máy B"]) --> L1{"Local file có dữ liệu?"}
L1 -- "có" --> RB
L1 -- "trống, Sync bật, live" --> C1{"CF domain có giá trị và load OK?"}
C1 -- "có" --> SF
C1 -- "không" --> M1{"MyApi load OK?"}
M1 -- "có" --> SF["TicketMapSaveFile(): cache local"]
M1 -- "không" --> RB
SF --> RB["TicketMapRebuild(): bổ sung position đang mở chưa có trong map"]
RB --> HB["Heartbeat đầu tiên: leader hay follower"]Nạp khi khởi động, đúng theo code 1.42. TicketMapRebuild() luôn chạy, không chỉ khi mọi nguồn đều lỗi.
flowchart LR
A["MT5 instance A (main)<br/>GoldenRobber"]
F[("Local file<br/>GR_TicketMap_magic.csv")]
CF[("Cloudflare Worker → D1<br/>/tmap/save")]
MY[("fxconsole → MySQL<br/>/easupported/goldenrobber/api/tmap/save")]
A -- "always" --> F
A -. "live, only when InpCloudCfDomain is set" .-> CF
A -- "live, leader only" --> MYSave: the local file on every call; the cloud on the InpCloudSyncIntervalSec cycle and only when the node is leader. Dashed = not used in production.
flowchart TB
S(["OnInit on machine B"]) --> L1{"Local file has data?"}
L1 -- "yes" --> RB
L1 -- "empty, Sync on, live" --> C1{"CF domain set and load OK?"}
C1 -- "yes" --> SF
C1 -- "no" --> M1{"MyApi load OK?"}
M1 -- "yes" --> SF["TicketMapSaveFile(): local cache"]
M1 -- "no" --> RB
SF --> RB["TicketMapRebuild(): add open positions missing from the map"]
RB --> HB["First heartbeat: leader or follower"]Loading at startup, as implemented in 1.42. TicketMapRebuild() always runs, not only when every source failed.
Sơ đồ gốc trong guideOriginal diagram from the guide
1. Local file → Primary, hoạt động cả backtest
2. Cloudflare D1 → Cloud backup #1
3. MySQL REST API → Cloud backup #2 (phòng CF sập)
MT5 Instance A (chính)
↓ save song song
├── Local file (luôn)
├── Cloudflare Worker → D1 (live only)
└── fxconsole API → MySQL (live only)
MT5 Instance A SẬP
MT5 Instance B (máy khác, cùng magic+account)
↓ load priority
1. Local file (TRỐNG vì máy mới)
2. Cloudflare D1 → có data ✓ → dùng
3. Nếu CF sập → MySQL → có data ✓ → dùng
4. Fallback: rebuild từ comment positions
1. Local file → Primary, works in backtests too
2. Cloudflare D1 → Cloud backup #1
3. MySQL REST API → Cloud backup #2 (in case CF is down)
MT5 Instance A (main)
↓ saves in parallel
├── Local file (always)
├── Cloudflare Worker → D1 (live only)
└── fxconsole API → MySQL (live only)
MT5 Instance A CRASHES
MT5 Instance B (other machine, same magic+account)
↓ load priority
1. Local file (EMPTY because the machine is new)
2. Cloudflare D1 → has data ✓ → use it
3. If CF is down → MySQL → has data ✓ → use it
4. Fallback: rebuild from position comments
fxconsole TMap API (MySQL)fxconsole TMap API (MySQL)
Endpoint hiện tại (theo từng EA)Current endpoints (per EA)
Base URL = InpCloudMyApiDomain + /api/tmap, ví dụ https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap. Header bắt buộc: Authorization: Bearer <secret>, trong đó secret là token của product trên /admin/easupported (EA đặt vào InpCloudBearerToken).
Base URL = InpCloudMyApiDomain + /api/tmap, for example https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap. Required header: Authorization: Bearer <secret>, where the secret is the product's token from /admin/easupported (set on the EA as InpCloudBearerToken).
| Method | Endpoint | Hành viBehaviour | Trả vềReturns |
|---|---|---|---|
| POST | /easupported/{slug}/api/tmap/save |
REPLACE ALL cho magic+account: xoá hết rồi insert mới — hoặc bỏ qua nếu fingerprint trùngREPLACE ALL for magic+account: delete everything, then insert — or skip if the fingerprint matches |
{"ok":true,"count":N} · {"ok":true,"count":N,"skipped":true} |
| GET | /easupported/{slug}/api/tmap/load?magic=&account= |
Đọc mapReads the map | {"ok":true,"magic","account","entries":[...]} |
| DELETE | /easupported/{slug}/api/tmap/clear?magic=&account= |
Xoá map và xoá fingerprintDeletes the map and its fingerprint | {"ok":true,"deleted":N} |
| POST | /easupported/{slug}/api/tmap/heartbeat |
Leader election + schema/telemetry + config hot-reloadLeader election + schema/telemetry + hot-reload config | xem Chuẩn tích hợp §5.3see Integration standard §5.3 |
- Mã lỗi:
404 Unknown EAkhi slug không tồn tại ·401 Unauthorizedkhi token trống hoặc không khớp (so bằnghash_equals) ·422khi thiếumagic/accounthoặc sai dữ liệu. - Route không dùng session, cookie, CSRF hay Inertia (
routes/web.php, prefixeasupported/{slug}/api/tmap); xác thực bearer nằm trong controller. - Validate
save:magicstring ≤32,accountstring ≤64,entriesarray; mỗi entry cóticket,round_id,add_num(integer),is_dca_pos(boolean),pos_type(0 hoặc 1),comment(tuỳ chọn, ≤255).
- Errors:
404 Unknown EAwhen the slug does not exist ·401 Unauthorizedwhen the token is empty or does not match (compared withhash_equals) ·422whenmagic/accountis missing or data is invalid. - The routes run without session, cookies, CSRF or Inertia (
routes/web.php, prefixeasupported/{slug}/api/tmap); bearer authentication happens in the controller. savevalidation:magicstring ≤32,accountstring ≤64,entriesarray; each entry hasticket,round_id,add_num(integer),is_dca_pos(boolean),pos_type(0 or 1),comment(optional, ≤255).
Bỏ ghi lại khi map không đổi (fxconsole 2026.09.04.1)Skipping rewrites of an unchanged map (fxconsole 2026.09.04.1)
EA đẩy toàn bộ ticket map mỗi InpCloudSyncIntervalSec (5–10s) kể cả khi không có gì đổi, và save trước đây xoá hết + insert hết mỗi lần: ghi 2N dòng mỗi lượt, đo được khoảng 10 ghi/giây, ~873k ghi/ngày, sinh ra khoảng 3 GB binlog mỗi ngày trên một database 179 MB và cuối cùng làm đầy ổ đĩa.
- Server tính fingerprint
sha1(magic|account|json(entries đã chuẩn hoá và sort))— không phụ thuộc thứ tự entry, vì EA không bảo đảm thứ tự giữa các lần đẩy. - Fingerprint lưu trong Redis key
tmap_fp:{magic}:{account}, TTL 3600s: dù fingerprint có sống lâu hơn dữ liệu, map vẫn được ghi lại ít nhất mỗi giờ một lần. - Trùng fingerprint → trả
"skipped":true, không ghi DB. Ghi thành công → lưu fingerprint mới.clear→ xoá fingerprint. - Redis không truy cập được → fail open: vẫn ghi DB, không bao giờ bỏ qua ghi dựa trên phỏng đoán.
- Áp dụng cho cả endpoint theo từng EA (
EaSupportedApiController) lẫn endpoint legacy (TmapApiController).
The EA pushes its full ticket map every InpCloudSyncIntervalSec (5–10s) even when nothing changed, and save used to delete everything + insert everything each time: 2N rows written per sync, measured at about 10 writes/second, ~873k writes/day, producing roughly 3 GB of binlog per day on a 179 MB database and eventually filling the disk.
- The server computes the fingerprint
sha1(magic|account|json(normalised, sorted entries))— independent of entry order, because the EA does not guarantee a stable order between pushes. - The fingerprint is stored in the Redis key
tmap_fp:{magic}:{account}with a TTL of 3600s: even if a fingerprint outlives its rows, the map is still rewritten at least once an hour. - Fingerprint matches → returns
"skipped":truewith no DB write. Successful write → the new fingerprint is stored.clear→ the fingerprint is deleted. - Redis unreachable → fail open: the DB write still happens; a write is never skipped on a guess.
- Applies to both the per-EA endpoints (
EaSupportedApiController) and the legacy endpoints (TmapApiController).
Request và responseRequests and responses
POST …/api/tmap/save
{
"magic": "21478308",
"account": "12345",
"entries": [
{
"ticket": 100,
"round_id": 5,
"add_num": 2,
"is_dca_pos": true,
"pos_type": 0,
"comment": "DCAPos R5 B#2 |add_minpips"
}
]
}
GET …/api/tmap/load?magic=21478308&account=12345
{
"ok": true,
"magic": "21478308",
"account": "12345",
"entries": [...]
}
DELETE …/api/tmap/clear?magic=21478308&account=12345
{ "ok": true, "deleted": 5 }
Test bằng curlTesting with curl
Endpoint theo từng EA (hiện tại). Thay <secret> bằng token của product; không dán token vào tài liệu hay lịch sử shell dùng chung.
Per-EA endpoints (current). Replace <secret> with the product's token; never paste the token into docs or a shared shell history.
curl -X POST https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap/save \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <secret>" \
-d '{"magic":"123","account":"test","entries":[]}'
curl "https://fxconsole.gamelau.uk/easupported/goldenrobber/api/tmap/load?magic=123&account=test" \
-H "Authorization: Bearer <secret>"
LEGACY Endpoint toàn cục trong guide gốc (your-server.com là placeholder):
LEGACY Global endpoints from the original guide (your-server.com is a placeholder):
curl -X POST https://your-server.com/api/tmap/save \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <secret>" \
-d '{"magic":"123","account":"test","entries":[]}'
curl "https://your-server.com/api/tmap/load?magic=123&account=test" \
-H "Authorization: Bearer <secret>"
Schema bảng tmap_entriestmap_entries table schema
| Column | Type | Ghi chúNote |
|---|---|---|
id | bigint | PK auto-increment |
magic | varchar(32) | EA magic number |
account | varchar(64) | MT5 account number |
ticket | unsigned bigint | Position ticket |
round_id | int | Round ID từ commentRound ID from the comment |
add_num | int | B#N hoặc S#N (0 = Init)B#N or S#N (0 = Init) |
is_dca_pos | bool | true=DCAPos, false=DCANeg |
pos_type | int | 0=Buy, 1=Sell |
comment | varchar(255) | Comment gốcOriginal comment |
created_at | timestamp | |
updated_at | timestamp |
Index: UNIQUE(magic, account, ticket) + INDEX(magic, account). Node leader election nằm ở bảng tmap_nodes (magic, account, node_id, priority, last_heartbeat).
Indexes: UNIQUE(magic, account, ticket) + INDEX(magic, account). Leader-election nodes live in the tmap_nodes table (magic, account, node_id, priority, last_heartbeat).
File liên quanRelated files
Mọi đường dẫn tính từ monitor/fxconsole/.
All paths are relative to monitor/fxconsole/.
| File | Chức năngPurpose |
|---|---|
database/migrations/2026_04_25_000001_create_tmap_entries_table.php | Schema bảng tmap_entriestmap_entries table schema |
app/Models/TmapEntry.php | Eloquent model + fingerprint (payloadFingerprint, fingerprintMatches, rememberFingerprint, forgetFingerprint)Eloquent model + fingerprint (payloadFingerprint, fingerprintMatches, rememberFingerprint, forgetFingerprint) |
app/Http/Controllers/Api/EaSupportedApiController.php | save/load/clear/heartbeat theo từng EA, xác thực bằng easupported.secret_tokenPer-EA save/load/clear/heartbeat, authenticated with easupported.secret_token |
routes/web.php | Route easupported/{slug}/api/tmap/*easupported/{slug}/api/tmap/* routes |
database/migrations/2026_08_29_000007_add_secret_token_to_easupported.php | Thêm cột secret_token; backfill bằng TMAP_SECRET_KEY toàn cục cũ (hoặc chuỗi ngẫu nhiên 40 ký tự nếu không có) để EA cũ vẫn xác thực đượcAdds the secret_token column; backfills it with the old global TMAP_SECRET_KEY (or a random 40-character string if none) so existing EAs keep authenticating |
app/Http/Controllers/Api/TmapApiController.php LEGACY | save/load/clear/heartbeat toàn cục /api/tmap/*Global /api/tmap/* save/load/clear/heartbeat |
routes/api.php LEGACY | Route /api/tmap/*/api/tmap/* routes |
config/app.php LEGACY | Config tmap_secret_keytmap_secret_key config |
.env.example LEGACY | TMAP_SECRET_KEY=<secret> |
SetupSetup
- Sau khi pull code mới:
php artisan migrate(hoặcbash deploy.shtrên server). Nếu có config mới:php artisan config:clear. - Tạo hoặc mở EA product trên
/admin/easupported, copy URLhttps://fxconsole.gamelau.uk/easupported/{slug}và secret token vàoInpCloudMyApiDomain/InpCloudBearerToken. Trang/admin/settingskhông còn trường TradingMap Secret.
- After pulling new code:
php artisan migrate(orbash deploy.shon the server). If config was added:php artisan config:clear. - Create or open the EA product on
/admin/easupportedand copy the URLhttps://fxconsole.gamelau.uk/easupported/{slug}and the secret token intoInpCloudMyApiDomain/InpCloudBearerToken. The/admin/settingspage no longer has a TradingMap Secret field.
cd monitor/fxconsole
php artisan migrate
php artisan config:clear
LEGACY Setup cũ cho /api/tmap/*: thêm TMAP_SECRET_KEY vào .env của fxconsole rồi migrate. Endpoint legacy vẫn nhận giá trị này nếu còn client cũ dựa vào nó.
LEGACY Old setup for /api/tmap/*: add TMAP_SECRET_KEY to fxconsole's .env, then migrate. The legacy endpoints still honour this value if an old client relies on it.
cd monitor/fxconsole
echo "TMAP_SECRET_KEY=<secret>" >> .env
php artisan config:clear
php artisan migrate
Cloudflare Worker + D1 (tuỳ chọn)Cloudflare Worker + D1 (optional)
- Worker vẫn xác thực bằng một secret toàn cục
TMAP_SECRET_KEY. EA gửi cùngInpCloudBearerTokentới cả hai backend, nên nếu bật CF thì secret của Worker phải bằng đúng token của productgoldenrobber. - Khi
InpCloudCfDomaincó giá trị, EA gọi heartbeat tới Cloudflare trước và dừng ở đó nếu thành công. Worker chỉ trả thông tin leader, không cóhot_keyshay config, không nhậnconfig_schema/payload— nên trong lúc CF khoẻ, hot-reload và EA Link sẽ không kéo được config từ fxconsole. Để trốngInpCloudCfDomain.
- The Worker still authenticates with one global secret,
TMAP_SECRET_KEY. The EA sends the sameInpCloudBearerTokento both backends, so if CF is enabled the Worker's secret must equal thegoldenrobberproduct token exactly. - When
InpCloudCfDomainis set, the EA sends its heartbeat to Cloudflare first and stops there on success. The Worker returns only leader information — nohot_keys, no config, and it does not acceptconfig_schema/payload— so while CF is healthy, hot-reload and EA Link cannot pull config from fxconsole. KeepInpCloudCfDomainempty.
Files
Thư mục cloudflare-tmap-worker/:
Folder cloudflare-tmap-worker/:
| File | Chức năngPurpose |
|---|---|
src/index.js | Code Worker: /tmap/save, /tmap/load, /tmap/clear, /tmap/heartbeat + Bearer authWorker code: /tmap/save, /tmap/load, /tmap/clear, /tmap/heartbeat + Bearer auth |
schema.sql | Schema D1: bảng tmap_entries với UNIQUE(magic, account, ticket) và tmap_nodesD1 schema: tmap_entries with UNIQUE(magic, account, ticket), and tmap_nodes |
wrangler.toml | Config Wrangler (D1 binding)Wrangler config (D1 binding) |
package.json | npm scriptsnpm scripts |
README.md | Hướng dẫn setup chi tiếtDetailed setup guide |
Trang dịch vụ: Cloudflare TMap worker.
Service page: Cloudflare TMap worker.
Setup từng bướcStep-by-step setup
- Cài Wrangler CLI. Trình duyệt sẽ mở để đăng nhập tài khoản Cloudflare.
cd cloudflare-tmap-worker npm install npm install -g wrangler wrangler login - Tạo D1 database.
npm run db:createOutput mẫu:
✅ Successfully created DB 'gr-tmap' [[d1_databases]] binding = "DB" database_name = "gr-tmap" database_id = "abc123-def456-7890-..."Copy
database_idvà dán vàowrangler.toml(thayREPLACE_WITH_YOUR_D1_DATABASE_ID):[[d1_databases]] binding = "DB" database_name = "gr-tmap" database_id = "abc123-def456-7890-..." # ← paste vào đây - Apply schema (tạo bảng).
npm run db:migrate - Đặt secret. Nhập secret khi được hỏi. Secret này phải GIỐNG token EA gửi trong
InpCloudBearerToken. LEGACY Guide gốc yêu cầu giốngTMAP_SECRET_KEYtrong.envcủa fxconsole vàInpCloudSecretKeytrong input EA.npm run secret:set - Deploy. URL in ra là giá trị cho
InpCloudCfDomain(guide gốc gọi làInpCloudCfUrl); EA tự nối thêm/tmap/{save,load,clear,heartbeat}.npm run deploy✨ Deployed gr-tmap triggers (1 sec) https://gr-tmap.your-account.workers.dev
- Install the Wrangler CLI. A browser opens to log in to the Cloudflare account.
cd cloudflare-tmap-worker npm install npm install -g wrangler wrangler login - Create the D1 database.
npm run db:createSample output:
✅ Successfully created DB 'gr-tmap' [[d1_databases]] binding = "DB" database_name = "gr-tmap" database_id = "abc123-def456-7890-..."Copy the
database_idand paste it intowrangler.toml(replacingREPLACE_WITH_YOUR_D1_DATABASE_ID):[[d1_databases]] binding = "DB" database_name = "gr-tmap" database_id = "abc123-def456-7890-..." # ← paste here - Apply the schema (create tables).
npm run db:migrate - Set the secret. Enter it when prompted. It MUST equal the token the EA sends in
InpCloudBearerToken. LEGACY The original guide required it to matchTMAP_SECRET_KEYin fxconsole's.envand the EA inputInpCloudSecretKey.npm run secret:set - Deploy. The printed URL is the value for
InpCloudCfDomain(calledInpCloudCfUrlin the original guide); the EA appends/tmap/{save,load,clear,heartbeat}itself.npm run deploy✨ Deployed gr-tmap triggers (1 sec) https://gr-tmap.your-account.workers.dev
Test Worker đã deployTesting the deployed Worker
curl -X POST https://gr-tmap.your-account.workers.dev/tmap/save \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <secret>" \
-d '{"magic":"123","account":"test","entries":[]}'
Giới hạn free tierFree tier limits
- D1: 5GB storage, 5M reads/ngày, 100k writes/ngày.
- Workers: 100k requests/ngày.
Guide gốc ước tính: EA save mỗi 30 giây + sự kiện partial close ≈ 3000 requests/ngày → đủ free. Lưu ý: với chu kỳ 10 giây như pass58, mỗi chu kỳ có 1 heartbeat + 1 save, tức khoảng 17,280 requests/ngày cho mỗi instance (ước tính số học) — vẫn dưới 100k nhưng nhiều hơn con số trong guide.
- D1: 5GB storage, 5M reads/day, 100k writes/day.
- Workers: 100k requests/day.
The original guide's estimate: the EA saves every 30 seconds + partial-close events ≈ 3000 requests/day → within the free tier. Note: with a 10-second cycle as in pass58, each cycle sends 1 heartbeat + 1 save, i.e. about 17,280 requests/day per instance (arithmetic estimate) — still below 100k, but well above the guide's figure.
EA GoldenRobber Cloud SyncGoldenRobber EA Cloud Sync
Input (1.42)Inputs (1.42)
| Input | Mặc địnhDefault | pass58 1.42 b3 | Mô tảDescription |
|---|---|---|---|
InpCloudSyncEnable | false | true | Bật/tắt đồng bộ cloud (chỉ live, không chạy trong backtest)Enables cloud sync (live only, never in backtests) |
InpCloudBearerToken | "" | <secret> | Bearer token: token của product trên /admin/easupported; gửi tới cả hai backendBearer token: the product's token from /admin/easupported; sent to both backends |
InpCloudCfDomain | "" | trốngempty | Domain Cloudflare Worker (vd https://gr-tmap.your-account.workers.dev); EA tự nối /tmap/{save,load,clear,heartbeat}Cloudflare Worker domain (e.g. https://gr-tmap.your-account.workers.dev); the EA appends /tmap/{save,load,clear,heartbeat} |
InpCloudMyApiDomain | "" | https://fxconsole.gamelau.uk/easupported/goldenrobber | URL product trên fxconsole; EA tự nối /api/tmap/{save,load,clear,heartbeat}The product's fxconsole URL; the EA appends /api/tmap/{save,load,clear,heartbeat} |
InpCloudSyncIntervalSec | 30 | 10 | Chu kỳ heartbeat + save cloud (giây)Heartbeat + cloud save cycle (seconds) |
InpCloudWebTimeoutMs | 5000 | 5000 | WebRequest timeout (ms)WebRequest timeout (ms) |
InpCloudNodePriority | 100 | 100 | Priority node (1 = cao nhất)Node priority (1 = highest) |
InpCloudNodeId | "" | trốngempty | Trống = auto từ đường dẫn terminalEmpty = auto from the terminal path |
InpCloudHeartbeatTimeoutSec | 90 | 90 | Coi node chết nếu không heartbeat trong N giâyA node is considered dead after N seconds without a heartbeat |
Comment của InpCloudMyApiDomain trong GoldenRobber-1.42.mq5 vẫn ghi “DOMAIN ONLY (vd https://fxconsole.gamelau.uk)”. Giá trị đó chỉ trỏ tới endpoint legacy /api/tmap/*. Với remote config theo từng EA và EA Link, giá trị PHẢI kèm /easupported/goldenrobber.
The comment on InpCloudMyApiDomain in GoldenRobber-1.42.mq5 still says “DOMAIN ONLY (e.g. https://fxconsole.gamelau.uk)”. That value only reaches the legacy /api/tmap/* endpoints. For per-EA remote config and EA Link the value MUST include /easupported/goldenrobber.
LEGACY Nhóm input thiết kế ban đầu trong guide (trước v1.38 đổi sang *Domain và trước khi có token theo product):
LEGACY The input group as originally designed in the guide (before v1.38 renamed them to *Domain, and before per-product tokens):
=== Cloud Sync (TMap) ===
InpCloudSyncEnable = false
InpCloudSecretKey = <secret>
InpCloudCfUrl = https://gr-tmap.your-account.workers.dev
InpCloudMyApiUrl = https://your-server.com/api/tmap
InpCloudSyncIntervalSec = 30
Input multi-node và leader election: xem Chuẩn tích hợp §10. Toàn bộ input: Tham số input.
Multi-node inputs and leader election: see Integration standard §10. Every input: Input parameters.
WebRequest whitelist (BẮT BUỘC)WebRequest whitelist (REQUIRED)
Mở MT5 → Tools → Options → Expert Advisors → Allow WebRequest for listed URL và thêm origin của mọi backend đang dùng (chỉ cần origin, không cần path). Production chỉ cần https://fxconsole.gamelau.uk; guide gốc thêm cả hai:
Open MT5 → Tools → Options → Expert Advisors → Allow WebRequest for listed URL and add the origin of every backend in use (origin only, no path). Production needs only https://fxconsole.gamelau.uk; the original guide adds both:
https://gr-tmap.your-account.workers.dev
https://your-server.com
WebRequest() KHÔNG hoạt động trong Strategy Tester. EA tự bỏ qua cloud sync khi MQLInfoInteger(MQL_TESTER) == true để backtest không bị chậm. WebRequest cũng yêu cầu HTTPS với cert hợp lệ; cert self-signed sẽ lỗi GetLastError = 4014.
WebRequest() does NOT work in the Strategy Tester. The EA skips cloud sync when MQLInfoInteger(MQL_TESTER) == true so backtests are not slowed down. WebRequest also requires HTTPS with a valid certificate; a self-signed certificate fails with GetLastError = 4014.
Dòng “Cloud” trên panelThe “Cloud” panel line
Khi InpCloudSyncEnable = true, panel hiện mã HTTP của lần save gần nhất tới mỗi backend: OK khi 2xx, - khi chưa gọi (hoặc domain trống), còn lại là mã lỗi. Màu lime khi mọi backend đã cấu hình đều OK, đỏ cam khi có backend lỗi.
When InpCloudSyncEnable = true, the panel shows the HTTP code of the latest save to each backend: OK for 2xx, - when never called (or the domain is empty), otherwise the error code. Lime when every configured backend is OK, orange-red when any backend fails.
Dòng 1: production (CF trống). Dòng 2: cả hai backend OK. Dòng 3: Cloudflare lỗi 500. Dòng 4: WebRequest tới MyApi lỗi (-1).
Line 1: production (CF empty). Line 2: both backends OK. Line 3: Cloudflare returned 500. Line 4: the WebRequest to MyApi failed (-1).
Khi nào EA lưuWhen the EA saves
TicketMapSave(): luôn ghi file local; gọiCloudSyncPulse(). Được gọi sau init mới, add DCA, Tia close, và mỗi 30 giây ở bước dọn map trongOnTick.CloudSyncPulse(): throttle theoInpCloudSyncIntervalSec, được gọi từ cảOnTicklẫnOnTimer(heartbeat chạy đúng chu kỳ dù không có tick). Mỗi lượt: heartbeat trước (leader + nhận config), rồi chỉ leader save lên cloud.TicketMapSaveImmediate(): bỏ qua throttle — sauPositionClosePartial(cập nhật ticket mới) và trongOnDeinit. Follower vẫn ghi file local nhưng không sync cloud.- Xoá cloud: khi map vừa về 0 (clean state),
TicketMapClearCloud()gửi DELETE tới mọi backend có domain. - Save gửi toàn bộ map (replace all), không append → không cần merge conflict. EA gửi mỗi chu kỳ dù map không đổi; server bỏ qua ghi DB (sai lệch D5).
TicketMapSave(): always writes the local file; callsCloudSyncPulse(). Called after a new init, an added DCA, a Tia close, and every 30 seconds in the map clean-up step inOnTick.CloudSyncPulse(): throttled byInpCloudSyncIntervalSec, called from bothOnTickandOnTimer(so the heartbeat keeps its cycle without ticks). Each pass: heartbeat first (leader + receive config), then only the leader saves to the cloud.TicketMapSaveImmediate(): bypasses the throttle — afterPositionClosePartial(to record the new ticket) and inOnDeinit. A follower still writes the local file but does not sync to the cloud.- Clearing the cloud: when the map has just dropped to 0 (clean state),
TicketMapClearCloud()sends DELETE to every backend with a domain. - Save sends the whole map (replace all), never appends → no merge conflicts. The EA sends every cycle even when the map is unchanged; the server skips the DB write (deviation D5).
TicketMapSave(): // file mỗi lần; cloud qua CloudSyncPulse (throttle InpCloudSyncIntervalSec)
├─ TicketMapSaveFile() (LUÔN — kể cả backtest)
└─ CloudSyncPulse() (Live + Enable, OnTick + OnTimer)
├─ TicketMapHeartbeat() (CF trước nếu có domain, rồi MyApi)
└─ if leader:
├─ TicketMapSyncCloudflare()
└─ TicketMapSyncMyApi()
TicketMapSaveImmediate(): // sự kiện quan trọng (partial close, OnDeinit)
├─ TicketMapSaveFile()
└─ if (Live + Enable):
├─ TicketMapHeartbeat()
└─ if leader: TicketMapSyncCloudflare() + TicketMapSyncMyApi()
Comments in the block above: TicketMapSave — file every time; cloud via CloudSyncPulse (throttled by InpCloudSyncIntervalSec); TicketMapSaveFile — ALWAYS, backtests included; TicketMapHeartbeat — CF first if it has a domain, then MyApi; TicketMapSaveImmediate — critical events (partial close, OnDeinit).
Khi khởi động (OnInit)At startup (OnInit)
1. TicketMapLoad() ← local file (nhanh nhất)
2. TicketMapLoadCloudflare() ← nếu local rỗng, sync bật, live, và có InpCloudCfDomain
3. TicketMapLoadMyApi() ← nếu CF không có dữ liệu / lỗi
(2 hoặc 3 thành công → TicketMapSaveFile() để cache local)
4. TicketMapRebuild() ← LUÔN chạy: bổ sung position đang mở chưa có trong map
5. TicketMapHeartbeat() ← heartbeat đầu tiên: leader / follower trước khi trade
Reading the block above: 1 — local file (fastest); 2 — only if local is empty, sync is on, live, and InpCloudCfDomain is set; 3 — if CF has no data or fails; when 2 or 3 succeeds, TicketMapSaveFile() caches it locally; 4 — ALWAYS runs, adding open positions missing from the map; 5 — the first heartbeat decides leader / follower before any trading.
LEGACY Phác thảo ban đầu trong guide (code thật khác ở hai điểm: TicketMapRebuild() luôn chạy bổ sung, và cloud chỉ do leader save):
LEGACY The original sketch from the guide (the real code differs in two ways: TicketMapRebuild() always runs as a supplement, and only the leader saves to the cloud):
void TicketMapSave() {
TicketMapSaveFile(); // Always: local file
if (!MQLInfoInteger(MQL_TESTER)) {
TicketMapSyncCloudflare(); // Live only: Cloudflare D1
TicketMapSyncMyApi(); // Live only: MySQL
}
}
void OnInit() {
// 1. Local file (nhanh nhất)
if (TicketMapLoadFile()) return;
// 2. Cloudflare D1
if (!MQLInfoInteger(MQL_TESTER)) {
if (TicketMapLoadCloudflare()) {
TicketMapSaveFile(); // cache local
return;
}
}
// 3. MySQL fallback
if (!MQLInfoInteger(MQL_TESTER)) {
if (TicketMapLoadMyApi()) {
TicketMapSaveFile();
return;
}
}
// 4. Rebuild từ comment positions (last resort)
TicketMapRebuild();
}
Khi nPos == 0 (clean state): gọi TicketMapClear() cho cả 3 nguồn để reset.
When nPos == 0 (clean state): call TicketMapClear() on all 3 sources to reset. (Sketch comments: “nhanh nhất” = fastest; “Rebuild từ comment positions” = rebuild from position comments.)
Kịch bản phục hồiRecovery scenarios
Production hiện tại — máy chính sập, CF trốngCurrent production — main machine crashes, CF empty
Máy A: GoldenRobber, magic=21478308, account=12345, InpCloudCfDomain trống
Mỗi InpCloudSyncIntervalSec (pass58 = 10s): heartbeat → nếu là leader: save lên fxconsole
(server bỏ qua ghi DB khi map không đổi)
Panel: Cloud: CF=- | MyApi=OK
Máy A SẬP
Máy B: gắn EA cùng .set (cùng magic, account, InpCloudMyApiDomain, token)
OnInit:
├─ Local file: TRỐNG (máy mới)
├─ Cloudflare: bỏ qua (domain trống)
├─ MyApi: load OK ✓ → TicketMapSaveFile() cache local
├─ TicketMapRebuild() bổ sung từ comment
└─ Heartbeat đầu tiên
Leader: A ngừng heartbeat; khi heartbeat cuối của A quá InpCloudHeartbeatTimeoutSec (90s)
thì B là node sống duy nhất → B thành leader và bắt đầu trade / save
Machine A: GoldenRobber, magic=21478308, account=12345, InpCloudCfDomain empty
Every InpCloudSyncIntervalSec (pass58 = 10s): heartbeat → if leader: save to fxconsole
(the server skips the DB write when the map is unchanged)
Panel: Cloud: CF=- | MyApi=OK
Machine A CRASHES
Machine B: attach the EA with the same .set (same magic, account, InpCloudMyApiDomain, token)
OnInit:
├─ Local file: EMPTY (new machine)
├─ Cloudflare: skipped (empty domain)
├─ MyApi: load OK ✓ → TicketMapSaveFile() caches locally
├─ TicketMapRebuild() fills in from comments
└─ First heartbeat
Leader: A stops heartbeating; once A's last heartbeat is older than
InpCloudHeartbeatTimeoutSec (90s), B is the only alive node → B becomes leader and starts trading / saving
Nếu A có priority cao hơn (số nhỏ hơn) và heartbeat cuối của A còn trong 90 giây, server vẫn coi A là leader nên B đứng ở chế độ follower cho tới khi A hết hạn. Đặt InpCloudNodePriority khác nhau cho mỗi node; chi tiết ở Chuẩn tích hợp §10.
If A has the higher priority (smaller number) and its last heartbeat is still within 90 seconds, the server still treats A as leader, so B stays a follower until A expires. Give every node a distinct InpCloudNodePriority; details in Integration standard §10.
Scenario A — MT5 chính sập, mở MT5 ở máy khác (có CF)Scenario A — main MT5 crashes, MT5 opened on another machine (with CF)
Máy A: GoldenRobber chạy với magic=21478308, account=12345
TMap save thường xuyên → Cloudflare D1 + fxconsole MySQL
Máy A SẬP (Windows crash, mất điện, internet down...)
Máy B: User attach EA cùng config (magic=21478308) trên account 12345
OnInit:
├─ Local file: TRỐNG (máy mới chưa có file)
├─ Cloudflare D1: load OK ✓ (X entries)
├─ TicketMapSaveFile() để cache local
└─ TicketMapRebuild() bổ sung từ comment
→ EA tiếp tục manage tickets như chưa từng sập
Machine A: GoldenRobber running with magic=21478308, account=12345
TMap saved regularly → Cloudflare D1 + fxconsole MySQL
Machine A CRASHES (Windows crash, power cut, internet down...)
Machine B: the user attaches the EA with the same config (magic=21478308) on account 12345
OnInit:
├─ Local file: EMPTY (the new machine has no file yet)
├─ Cloudflare D1: load OK ✓ (X entries)
├─ TicketMapSaveFile() to cache locally
└─ TicketMapRebuild() fills in from comments
→ the EA keeps managing tickets as if nothing had crashed
Scenario B — Cloudflare D1 sậpScenario B — Cloudflare D1 is down
Máy A: Save → CF lỗi → MyApi OK ✓
File local + MyApi vẫn save thành công
Panel hiển thị: Cloud: CF=500 | MyApi=OK (đỏ)
Máy B (mới khởi động):
OnInit → CF fail → fallback MyApi → load OK ✓
Machine A: Save → CF error → MyApi OK ✓
Local file + MyApi still save successfully
Panel shows: Cloud: CF=500 | MyApi=OK (red)
Machine B (just started):
OnInit → CF fails → falls back to MyApi → load OK ✓
Scenario C — Cả CF + MyApi sậpScenario C — both CF and MyApi are down
Máy A: Save → cả 2 fail → chỉ local file save thành công
Panel: Cloud: CF=-1 | MyApi=-1 (đỏ)
EA log warning, vẫn hoạt động bình thường (file local OK)
Máy B (mới khởi động):
OnInit → CF fail → MyApi fail → fallback TicketMapRebuild()
Rebuild map từ comment positions đang mở
→ Mất data partial close trước đó nhưng EA vẫn track positions hiện tại
Machine A: Save → both fail → only the local file saves successfully
Panel: Cloud: CF=-1 | MyApi=-1 (red)
The EA logs a warning and keeps running normally (local file OK)
Machine B (just started):
OnInit → CF fails → MyApi fails → falls back to TicketMapRebuild()
Rebuilds the map from the comments of open positions
→ earlier partial-close data is lost, but the EA still tracks current positions
Trong production (CF trống), “MyApi sập” rơi thẳng vào nhánh này. Nếu fxconsole sập lâu, heartbeat thất bại quá 2 × InpCloudHeartbeatTimeoutSec thì EA tự nhận leader để không bỏ mặc vị thế.
In production (CF empty), “MyApi down” lands directly in this branch. If fxconsole stays down, once heartbeats have failed for more than 2 × InpCloudHeartbeatTimeoutSec the EA takes leadership itself so positions are not abandoned.
EA luôn hoạt động được kể cả khi mọi cloud backend cùng sập. Local file là lưu trữ chính; cloud là phương án phục hồi cho trường hợp đổi máy.
The EA always keeps working, even when every cloud backend is down at once. The local file is the primary store; the cloud is the recovery path for moving to another machine.
Bảo trìMaintenance
Kiểm tra TMap đã syncChecking that TMap is synced
# fxconsole MySQL
cd monitor/fxconsole
php artisan tinker
>>> App\Models\TmapEntry::where('magic', '21478308')->count();
>>> App\Models\TmapEntry::where('magic', '21478308')->get();
# Cloudflare D1 (nếu dùng / if used)
wrangler d1 execute gr-tmap --remote \
--command="SELECT COUNT(*) FROM tmap_entries WHERE magic='21478308'"
wrangler d1 execute gr-tmap --remote --command="SELECT * FROM tmap_entries LIMIT 10"
Dọn dữ liệu cũ (định kỳ)Cleaning up old data (periodically)
Xoá entry không được cập nhật quá 30 ngày:
Delete entries not updated for more than 30 days:
# MySQL
cd monitor/fxconsole
php artisan tinker
>>> App\Models\TmapEntry::where('updated_at', '<', now()->subDays(30))->delete();
# D1
wrangler d1 execute gr-tmap --remote \
--command="DELETE FROM tmap_entries WHERE updated_at < datetime('now', '-30 days')"
Sau khi đổi codeAfter code changes
# fxconsole — sau khi pull code mới / after pulling new code
cd monitor/fxconsole
php artisan migrate
php artisan config:clear
# Cloudflare Worker — deploy production
cd cloudflare-tmap-worker
npm run deploy
npm run db:migrate # nếu đổi schema D1 / if the D1 schema changed
Bảo mậtSecurity notes
- Token theo product: mỗi EA product có secret riêng (
easupported.secret_token). LEGACY Guide gốc dùng một secret giống nhau ở cả 3 nơi (input EA, secret Cloudflare Worker,.envfxconsole). - Chỉ HTTPS — không dùng HTTP.
- Xoay token: bấm Regenerate trên
/admin/easupportedrồi cập nhật.setcủa mọi terminal (và secret Worker nếu có bật CF) cùng lúc. LEGACY Guide gốc: cập nhật cả 3 nơi cùng lúc. - Whitelist URL trong MT5 Options → Expert Advisors.
- Account number trong API là string — nếu broker đổi định dạng thì cần cập nhật.
- Không đưa token vào tài liệu, log hay
.setđược commit; dùng<secret>trong ví dụ.
- Per-product token: each EA product has its own secret (
easupported.secret_token). LEGACY The original guide used one identical secret in all 3 places (EA input, Cloudflare Worker secret, fxconsole.env). - HTTPS only — never HTTP.
- Rotating the token: click Regenerate on
/admin/easupported, then update the.seton every terminal (and the Worker secret if CF is enabled) at the same time. LEGACY Original guide: update all 3 places at the same time. - Whitelist the URLs in MT5 Options → Expert Advisors.
- The account number is a string in the API — if the broker changes its format, this needs updating.
- Never put the token in docs, logs or committed
.setfiles; use<secret>in examples.
Xử lý sự cốTroubleshooting
EA không syncThe EA does not sync
- Kiểm tra WebRequest whitelist trong MT5 Options.
- Kiểm tra
InpCloudSyncEnable = true. - Xem log MT5 (tab Experts) — sẽ có thông báo lỗi.
- Test API trực tiếp bằng curl (ví dụ).
- Panel
MyApi=-1: lỗi mạng hoặc host chưa có trong Allow WebRequest.
- Check the WebRequest whitelist in MT5 Options.
- Check
InpCloudSyncEnable = true. - Read the MT5 log (Experts tab) — it will contain an error message.
- Test the API directly with curl (examples).
- Panel
MyApi=-1: network error, or the host is not in Allow WebRequest.
MyApi 404 / 401 (endpoint theo từng EA)MyApi 404 / 401 (per-EA endpoints)
404: slug trongInpCloudMyApiDomainkhông tồn tại trên/admin/easupported.401:InpCloudBearerTokenkhác token của product (có thể vừa bấm Regenerate). Copy lại token vào.set.- Domain thiếu
/easupported/goldenrobber: EA đang gọi endpoint legacy/api/tmap/*, vốn dùng secret toàn cục.
404: the slug inInpCloudMyApiDomaindoes not exist on/admin/easupported.401:InpCloudBearerTokendiffers from the product's token (perhaps Regenerate was just clicked). Copy the token into the.setagain.- Domain without
/easupported/goldenrobber: the EA is calling the legacy/api/tmap/*endpoints, which use the global secret.
MySQL 401 UnauthorizedMySQL 401 Unauthorized LEGACY
TMAP_SECRET_KEYtrong.envkhác input EA.- Nạp lại config Laravel:
php artisan config:clear.
TMAP_SECRET_KEYin.envdiffers from the EA input.- Reload the Laravel config:
php artisan config:clear.
Cloudflare 401 UnauthorizedCloudflare 401 Unauthorized
- Secret trong EA khác secret đã đặt qua
wrangler secret put. - Đặt lại:
npm run secret:set.
- The secret in the EA differs from the one set via
wrangler secret put. - Set it again:
npm run secret:set.
D1 không có dữ liệu sau khi saveD1 has no data after a save
- Kiểm tra
database_idtrongwrangler.tomlđúng. - Chạy
npm run db:migrateđể chắc chắn schema đã được apply. - Test save bằng curl xem có lỗi không.
- Check that
database_idinwrangler.tomlis correct. - Run
npm run db:migrateto make sure the schema is applied. - Test a save with curl and look for errors.
Legacy và hiện tạiLegacy vs current
| Thành phầnComponent | LEGACY | HIỆN TẠICURRENT |
|---|---|---|
| Endpoint MySQLMySQL endpoints | /api/tmap/{save,load,clear,heartbeat} (TmapApiController) |
/easupported/{slug}/api/tmap/{save,load,clear,heartbeat} (EaSupportedApiController) |
| Auth | Secret toàn cục TMAP_SECRET_KEY (tmap_secret_key)Global secret TMAP_SECRET_KEY (tmap_secret_key) |
Token theo product easupported.secret_token, quản lý trên /admin/easupportedPer-product token easupported.secret_token, managed on /admin/easupported |
| Input EAEA inputs | InpCloudSecretKey, InpCloudCfUrl, InpCloudMyApiUrl (…/api/tmap) |
InpCloudBearerToken, InpCloudCfDomain, InpCloudMyApiDomain (https://host/easupported/goldenrobber); đổi sang *Domain từ v1.38, EA tự nối pathInpCloudBearerToken, InpCloudCfDomain, InpCloudMyApiDomain (https://host/easupported/goldenrobber); renamed to *Domain in v1.38, the EA appends the path |
| Trang settingsSettings page | Trường TradingMap SecretTradingMap Secret field | Đã gỡ; mỗi product tự giữ tokenRemoved; each product holds its own token |
| Cloudflare | Backup #1, secret toàn cụcBackup #1, global secret | Vẫn secret toàn cục; InpCloudCfDomain trống trong productionStill a global secret; InpCloudCfDomain empty in production |
Ghi savesave writes |
Xoá hết + insert hết mỗi lượtDelete all + insert all on every sync | Bỏ qua khi fingerprint trùng (fxconsole 2026.09.04.1, cả hai controller)Skipped when the fingerprint matches (fxconsole 2026.09.04.1, both controllers) |
Cây file (guide gốc)File tree (original guide)
GoldenRobber/
├── monitor/fxconsole/ # Laravel MySQL backend
│ ├── app/Http/Controllers/Api/
│ │ └── TmapApiController.php
│ ├── app/Models/
│ │ └── TmapEntry.php
│ ├── database/migrations/
│ │ └── 2026_04_25_000001_create_tmap_entries_table.php
│ ├── routes/api.php
│ ├── config/app.php
│ └── .env.example
│
├── cloudflare-tmap-worker/ # CF Worker + D1
│ ├── src/index.js
│ ├── schema.sql
│ ├── wrangler.toml
│ ├── package.json
│ └── README.md
│
└── docs/TMAP_DISASTER_RECOVERY_GUIDE.md # File này
Bổ sung từ khi có endpoint theo từng EA: app/Http/Controllers/Api/EaSupportedApiController.php, routes/web.php, database/migrations/2026_08_29_000007_add_secret_token_to_easupported.php (xem File liên quan).
(“File này” = this file.) Added since the per-EA endpoints: app/Http/Controllers/Api/EaSupportedApiController.php, routes/web.php, database/migrations/2026_08_29_000007_add_secret_token_to_easupported.php (see Related files).