Commit Graph
122 Commits
Author SHA1 Message Date
tiennm99 35372d25d7 fix(stock): expose ssi quote response errors 2026-06-25 16:12:12 +07:00
tiennm99 5e8f457995 docs(stock): record ssi provider research 2026-06-25 15:52:43 +07:00
tiennm99 e15f6ae1f1 fix(stock): use ssi price provider 2026-06-25 15:51:33 +07:00
tiennm99 d02a110d8e fix(modules): fetch stats prices sequentially to reuse pooled connections
Parallelizing the per-symbol fetches opened N simultaneous TLS handshakes into
an empty connection pool. On the memory-constrained Lambda (256MB ~0.15 vCPU)
those CPU-bound handshakes thrashed and each exceeded the per-fetch timeout, so
every ticker rendered "(no price)". Sequential fetches reuse the price client's
keep-alive connection (one handshake), which is why the code was sequential by
design.

- revert stock and coin stats loops to sequential (keep the reply-budget
  sub-context and 3s per-fetch timeout)
- log per-symbol fetch failures instead of silently swallowing them
2026-06-25 14:28:25 +07:00
tiennm99 97f376025d fix(modules): reserve reply budget and cap price-fetch timeouts in stats handlers
Stats handlers reused the single bounded update context for both upstream
price fetches and the final Telegram reply, while per-upstream HTTP timeouts
equalled the whole-handler budget. One slow upstream drained the deadline and
the reply failed with "context deadline exceeded" (observed on /stock_stats).

- add chathelper.FetchContext: fetches run under a child context that reserves
  a tail of the deadline for the reply, which is sent on the original context
- cap kbs/coin/gold/vnappmob HTTP timeouts at 3s so one hung upstream cannot
  consume the reply budget
- fetch held stock/coin prices concurrently (bounded) so total latency tracks
  the slowest single fetch instead of their sum; per-symbol failures degrade to
  a "no price" line and the summary still sends
2026-06-25 14:07:59 +07:00
tiennm99 0c649e3963 docs(plan): add coin sell message plan 2026-06-20 09:52:35 +07:00
tiennm99 68cc1486ea fix(coin): improve sell insufficient message 2026-06-20 09:52:23 +07:00
tiennm99 919fa74665 fix(coin): sell command accepts USD amount with dust and zero-price guards
- Change /coin_sell semantics from quantity to USD amount

- Add price-validity and dust-quantity guards to buy/sell

- Improve usage text and error messages

- Add tests for invalid amount, dust, zero price, and insufficient holdings
2026-06-16 17:27:39 +07:00
tiennm99 9eef87ad52 fix(coin): use Binance public market-data endpoint data-api.binance.vision 2026-06-15 14:09:11 +07:00
tiennm99 af993d640d docs(plans): add VNAppMob key parsing bug-fix plan 2026-06-15 12:20:44 +07:00
tiennm99 67387ff350 fix(gold): parse VNAppMob JWT object wrapper 2026-06-15 12:20:40 +07:00
tiennm99 c889d77472 fix(gold): use GET for VNAppMob key refresh endpoint
VNAppMob /api/request_api_key returns 405 for POST and accepts GET.
2026-06-15 11:17:50 +07:00
tiennm99 abde3a1c87 feat(gold): remove XAU/USD fallback and use VNAppMob SJC only 2026-06-15 11:04:06 +07:00
tiennm99 ae2b97ed26 chore(plans): add VNAppMob integration plan and completion report 2026-06-15 10:49:19 +07:00
tiennm99 57356ac312 docs(gold): document VNAppMob SJC pricing and env vars 2026-06-15 10:49:19 +07:00
tiennm99 b37a8dcd6f feat(gold): integrate VNAppMob SJC gold price with auto-refresh API key 2026-06-15 10:49:19 +07:00
tiennm99 2dc78733a1 chore: remove one-time stock-key migration tool
Migration complete and verified in production; the trading->stock
DynamoDB partition copy is no longer needed and the old trading rows
have been deleted.
2026-06-12 15:46:03 +07:00
tiennm99 5f0f6f3fcd fix(migrate): check fmt.Fprintf return to satisfy errcheck lint 2026-06-12 15:12:07 +07:00
tiennm99 e6ad2891dd refactor(stock): rename trading module to stock
Rename the `trading` module to `stock` across code, config, and docs for
naming consistency with the sibling coin/gold modules.

- Module: internal/modules/trading -> internal/modules/stock (package,
  factory, registry key in cmd/server)
- Commands: trade_* -> stock_* (telegram-commands.json)
- Env/SAM params: TRADING_INCOME_EVENTS_* -> STOCK_INCOME_EVENTS_*
- ModulesCSV updated in template.yaml, samconfig.toml, deploy.yml
- Add cmd/migrate-stock-key: idempotent, non-destructive DynamoDB
  partition copy (pk=trading -> pk=stock) with dry-run default

Production DynamoDB data already migrated (8 keys: 2 portfolios + 6
symbol-cache rows); old trading partition retained as rollback snapshot.
2026-06-12 14:52:12 +07:00
tiennm99 8b4690df6a fix(deploy): include coin module in ModulesCSV overrides
coin was registered in the catalog and template default but absent from
the ModulesCSV override in both deploy.yml and samconfig.toml, so the
deployed Lambda never loaded it and all coin_* commands went unregistered.

Add a registration checklist to docs/deploy-aws.md to prevent recurrence.
2026-06-12 11:22:16 +07:00
tiennm99 73be8415e7 fix(coin): accept symbol-first trades 2026-06-12 11:07:27 +07:00
tiennm99 31f6bc0a32 feat(coin): register telegram commands 2026-06-12 10:53:19 +07:00
tiennm99 3cb176d5b2 feat(coin): add crypto paper trading 2026-06-12 10:47:02 +07:00
tiennm99 89ea8f428d feat(gold): add gold commands to Telegram menu 2026-06-11 22:28:24 +07:00
tiennm99 2be3303563 fix(deploy): include gold module in CI ModulesCSV override
CI's --parameter-overrides replaces samconfig.toml values, so every
push reverted the stack to the pre-gold module list.
2026-06-11 22:21:19 +07:00
tiennm99 1293377584 feat(gold): add multi-provider gold price fallback chain
Replace goldprice.org (403s datacenter IPs) with a provider chain:
gold-api.com -> Swissquote public quotes -> NBP daily fixing (PLN/gram
converted via the shared FX table). Per-provider failures are logged;
a full-chain outage surfaces as a retryable error instead of the
silent no-price reply.
2026-06-11 22:11:30 +07:00
tiennm99 e099e8a766 fix(deploy): enable gold module in ModulesCSV 2026-06-11 21:57:05 +07:00
tiennm99 1c74cfc9c6 fix(gold): drop redundant embedded selectors in portfolio tests 2026-06-11 21:49:03 +07:00
tiennm99 3cad105a81 fix(storage): report missing CAS support as unsupported instead of conflict
- prefix.go: return errors.ErrUnsupported when inner store lacks CompareAndSwap, enabling fail-fast instead of infinite retries
- Adds comprehensive CAS semantics test coverage for all KV backends:
  * memory_kv_test.go: new tests for basic operations and CAS failures
  * prefix_test.go: tests for wrapped CAS errors and unsupported operations
  * dynamodb_kv_test.go, firestore_kv_test.go: CAS failure scenarios
- portfolio_test.go: test retry exhaustion, business-error short-circuit, concurrent updates, and fail-fast on unsupported CAS
2026-06-11 21:40:16 +07:00
tiennm99 0a795b1436 feat(gold): add compare-and-swap portfolio updates with multi-backend support
Implement atomic UpdatePortfolio with retry pattern for concurrent-safe updates.
Add CAS support to DynamoDB, Firestore, Memory, and prefix stores.
2026-06-11 21:12:58 +07:00
tiennm99 a826525145 feat(gold): add /gold_price command showing spot price in USD and VND
Read-only price lookup. Displays XAU USD/oz, USD/VND rate, and VND/luong.
No arguments, no portfolio mutation, no keylock needed.
2026-06-11 18:13:39 +07:00
tiennm99 2304657cb6 feat(gold): add gold paper trading module
Opt-in module for gold paper trading with VND currency.
Commands: /gold_topup, /gold_buy, /gold_sell, /gold_stats.
Pricing: spot XAU USD converted to VND per luong via GoldPrice.org + ExchangeRate-API.
Features: FX caching, dust normalization (1e-9), HTTPS-only URL validation,
per-user key locking, namespace isolation from trading module.
2026-06-11 17:13:02 +07:00
tiennm99 7c74917d80 fix: simplify trade income event fetcher 2026-06-05 14:26:57 +07:00
tiennm99 340172206a feat: add trade income events check 2026-06-05 14:21:38 +07:00
tiennm99 8b6f8b9ce7 feat: register trade income commands 2026-06-05 13:58:22 +07:00
tiennm99 442876c812 feat(trading): show remaining VND after buy/sell success 2026-06-05 08:48:45 +07:00
tiennm99 daeaf0c605 chore: drop CF→AWS migration tooling and stale JS-port references
The CF→AWS data migration (closed 2026-05-16) is long done and the
tooling isn't wired into any production path. Remove the one-shot binary,
its support package, and the migration runbook.

In live code, replace 'JS-parity' / 'same shape as JS' / 'cross-runtime
KV migration' comments with the real, stable reason for each behavior
(wire-format invariant, null-vs-zero distinction, CloudWatch alarm field
name, etc.). 24 files touched across lolschedule, loldle, wordle, twentyq,
trading, misc, util, server, metrics, ai, keylock.

- delete cmd/migrate_cf_data/
- delete internal/migration/
- delete docs/cf-to-aws-migration-runbook.md
2026-05-25 09:39:17 +07:00
tiennm99 2313be5024 fix(lolschedule): route daily push to originating forum topic
Subscribers previously stored only chatID; the daily-push cron sent
without MessageThreadID so Telegram routed to General regardless of the
topic the user subscribed from.

- Storage shape: []Subscriber{ChatID, ThreadID} keyed by (ChatID, ThreadID)
  so the same chat can subscribe in multiple topics independently.
- Decoder accepts legacy []int64 rows (ThreadID=0); successful mutations
  rewrite the slot in the new shape, no manual migration needed.
- Cron forwards MessageThreadID on every send.
- Auto-prune classifies terminal errors: chat-wide kills (bot blocked,
  deactivated, kicked, deleted, group upgraded) wipe every entry for the
  ChatID; topic-only "have no rights to send" prunes just (ChatID, ThreadID).
2026-05-25 09:39:01 +07:00
tiennm99 a5c5bd9da9 feat(stats): add per-user analytics with /stats subcommands
Extend the stats module to track per-user counts in addition to the
existing per-command totals. CommandHook now receives the originating
*models.Update so the hook can attribute invocations to a user; only
the stats module consumes this hook today.

Schema (sort keys under pk="stats"):
  count:<cmd>            existing, per-command total
  user:<id>              new, per-user total with cached username
  pair:<cmd>:<id>        new, per (command, user) pair

Subcommands (all public):
  /stats                 top commands (unchanged)
  /stats users           top users overall
  /stats user <name>     top commands for that user
  /stats cmd <name>      top users of that command

When the sender has no Telegram username the per-user/pair writes are
skipped — the global per-command counter still increments. View helpers
fan out KV reads in parallel to stay inside the webhook deadline. The
existing read-modify-write race is unchanged; closing it would need
atomic UpdateItem ADD on the KV interface.
2026-05-22 18:01:12 +07:00
tiennm99 ab53d9639f fix(deploy): include stats in CI ModulesCSV override
SAM CLI's --parameter-overrides replaces samconfig.toml's overrides
rather than merging them, so every CI deploy reset the stack's
ModulesCSV to the prior value (which predated the stats module).
The live Lambda's MODULES env was missing stats, so /stats was
dispatched but never handled — the module factory was never called.
2026-05-22 17:27:26 +07:00
tiennm99 ed0c5c1d7b fix(stats): fan out per-key GetItem to avoid handler deadline timeout
Sequential List + N GetItem made /stats latency scale with the number
of tracked commands. On a cold Lambda container with ~25 commands the
cumulative DynamoDB round-trips can exceed the 10s webhook handler
deadline; the trailing chathelper.Reply -> b.SendMessage then fails on
a cancelled ctx and the dispatcher only logs the error, leaving the
user with no reply at all.

Fan the per-key GetJSONs out into goroutines joined by sync.WaitGroup.
Wall-clock latency collapses to ~1 round-trip while preserving the
per-key error isolation (a single GetJSON failure still drops only its
own row). Storage interface unchanged; no race regression vs. the prior
per-item write pattern.
2026-05-22 16:36:57 +07:00
tiennm99 5e40ffe637 fix(deploy): hardcode non-secret CFN params instead of sed-parsing samconfig
The sed-based extraction from samconfig.toml worked locally (BusyBox sed)
but failed in CI with GNU sed:
  sed: -e expression #1, char 40: invalid reference \1 on 's' command's RHS

After YAML literal and bash single-quote unescaping, the sed pattern's
parens lacked the BRE \(...\) escaping, so there was no capture group
and \1 was invalid. Rather than fight escape layers for a non-secret
value, just inline the IDs in the workflow.
2026-05-22 15:43:07 +07:00
tiennm99 85579e56ef fix(deploy): pass BotOwnerID and AdminUserIDs from samconfig.toml in CI
CI's --parameter-overrides replaces (not merges with) samconfig.toml's
parameter_overrides, so BOT_OWNER_ID was empty in the deployed Lambda.
deploynotify silently skipped sending the post-deploy DM, and all
Protected/Private commands were denied.

Extract the values from samconfig.toml at deploy time so the file
remains the single source of truth for non-secret deploy params.

Also add 'stats' to samconfig.toml's ModulesCSV so local `sam deploy`
doesn't regress the stats module.
2026-05-22 15:39:42 +07:00
tiennm99 db8ee9c49b fix(stats): unblock deploy and register /stats in Telegram command menu
- Remove ReservedConcurrentExecutions: 1 (account lacks unreserved
  concurrency headroom; deploy rolled back). Accept best-effort
  counter semantics as documented.
- Add /stats to aws/telegram-commands.json so it appears in the
  Telegram client's / autocomplete after the next deploy.
2026-05-22 15:30:28 +07:00
tiennm99 a422913972 fix(dispatcher): place nolint:gosec on go statement, not inside goroutine 2026-05-22 15:14:07 +07:00
tiennm99 a1ed521c0e fix(stats): resolve golangci-lint G118 and QF1001 violations 2026-05-22 15:10:26 +07:00
tiennm99 3f1f264e4a feat(stats): add command usage statistics module with persistence
Implement a new stats module for the Telegram bot that tracks per-command usage with persistent KV storage. The module provides a /stats command displaying usage sorted by popularity with a 4096-byte Telegram message cap. Includes CommandHook integration for post-dispatch tracking via background goroutine (2s bounded context), proper test coverage, and registry initialization. Updated server config with stats factory and reserved concurrent execution control to prevent TOCTOU issues.
2026-05-22 15:06:46 +07:00
tiennm99 0475a69b89 chore(plans): add deploy-notify-owner plan (completed)
Plan + 2 phase files for the deploynotify feature. Both phases
marked completed.
2026-05-22 11:40:34 +07:00
tiennm99 a34320bc77 feat(deploynotify): DM owner once per new deployed git SHA
New internal/deploynotify package fires a single Telegram DM to
BOT_OWNER_ID on the first cold start that observes a new gitSHA.
Dedup via a DynamoDB KV record so repeat cold starts of the same
version stay silent. Send-then-write order means a failed Telegram
call doesn't permanently silence retries.

gitSHA is baked into the binary via `-ldflags "-X main.gitSHA=..."`
from Makefile; empty SHA (non-make builds) silently disables the
feature. No new env vars or IAM permissions.
2026-05-22 11:40:25 +07:00
tiennm99 33d88b97cc fix(lolschedule): paginate older pages to cover past days in week range
The default lolesports schedule page is anchored near "now", so a
calendar-aligned /lolschedule_week issued midweek was silently dropping
events from Mon–Wed: we only walked pages.newer. fetchSchedulePage now
also returns pages.older, and fetchEventsInRange walks older until the
earliest collected event is ≤ from, then walks newer until ≥ to. Page
budget bumped 3 → 8 to accommodate dense regular-season weeks.
2026-05-22 11:03:28 +07:00