mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-09-06 22:17:51 +00:00
refactor(config): remove price URL env overrides
This commit is contained in:
+2
-1
@@ -39,4 +39,5 @@ WC_FOOTBALL_DATA_TOKEN=
|
||||
# PORT — defaults to 8080 (internal health server)
|
||||
# TELEGRAM_WEBHOOK_SECRET — long polling has no webhook
|
||||
# GOLD_VNAPP_API_KEY — gold module auto-fetches + caches the key to Mongo
|
||||
# STOCK/COIN/GOLD *_API_URL overrides — modules use their coded default providers
|
||||
# Stock/coin/gold URL env overrides are not supported; modules use coded
|
||||
# default providers.
|
||||
|
||||
+20
-50
@@ -86,13 +86,6 @@ func main() {
|
||||
if cfg.TelegramBotToken == "" {
|
||||
log.Fatal("missing required env", "key", "TELEGRAM_BOT_TOKEN")
|
||||
}
|
||||
exportOptionalEnv("GOLD_PRICE_API_URL", cfg.GoldPriceAPIURL)
|
||||
exportOptionalEnv("GOLD_FX_API_URL", cfg.GoldFXAPIURL)
|
||||
exportOptionalEnv("GOLD_VNAPP_API_URL", cfg.GoldVNAppAPIURL)
|
||||
exportOptionalEnv("GOLD_VNAPP_API_KEY", cfg.GoldVNAppAPIKey)
|
||||
exportOptionalEnv("COIN_BINANCE_API_URL", cfg.CoinBinanceAPIURL)
|
||||
exportOptionalEnv("COIN_COINBASE_API_URL", cfg.CoinCoinbaseAPIURL)
|
||||
exportOptionalEnv("COIN_COINGECKO_API_URL", cfg.CoinCoinGeckoAPIURL)
|
||||
|
||||
// Periodic metrics flush. Cancels with rootCtx and emits one final
|
||||
// flush on shutdown so the trailing window isn't lost.
|
||||
@@ -264,23 +257,16 @@ func buildProvider(ctx context.Context, cfg config) (storage.Provider, func(), e
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Port string
|
||||
TelegramBotToken string
|
||||
SourceCommit string // Coolify-injected commit SHA (runtime env) for deploynotify
|
||||
GeminiAPIKey string
|
||||
GoldPriceAPIURL string
|
||||
GoldFXAPIURL string
|
||||
GoldVNAppAPIURL string
|
||||
GoldVNAppAPIKey string
|
||||
CoinBinanceAPIURL string
|
||||
CoinCoinbaseAPIURL string
|
||||
CoinCoinGeckoAPIURL string
|
||||
Modules []string
|
||||
BotOwnerID int64
|
||||
AdminUserIDs map[int64]bool
|
||||
KVProvider string // empty = auto-detect; or "memory"|"mongodb"
|
||||
MongoURL string // required when KVProvider=mongodb (Atlas SRV connection string; SECRET — never log)
|
||||
MongoDatabase string // required when KVProvider=mongodb
|
||||
Port string
|
||||
TelegramBotToken string
|
||||
SourceCommit string // Coolify-injected commit SHA (runtime env) for deploynotify
|
||||
GeminiAPIKey string
|
||||
Modules []string
|
||||
BotOwnerID int64
|
||||
AdminUserIDs map[int64]bool
|
||||
KVProvider string // empty = auto-detect; or "memory"|"mongodb"
|
||||
MongoURL string // required when KVProvider=mongodb (Atlas SRV connection string; SECRET — never log)
|
||||
MongoDatabase string // required when KVProvider=mongodb
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
@@ -301,32 +287,16 @@ func loadConfig() config {
|
||||
log.Fatal("invalid PORT", "value", port)
|
||||
}
|
||||
return config{
|
||||
Port: port,
|
||||
TelegramBotToken: envMap["TELEGRAM_BOT_TOKEN"],
|
||||
SourceCommit: envMap["SOURCE_COMMIT"],
|
||||
GeminiAPIKey: envMap["GEMINI_API_KEY"],
|
||||
GoldPriceAPIURL: envMap["GOLD_PRICE_API_URL"],
|
||||
GoldFXAPIURL: envMap["GOLD_FX_API_URL"],
|
||||
GoldVNAppAPIURL: envMap["GOLD_VNAPP_API_URL"],
|
||||
GoldVNAppAPIKey: envMap["GOLD_VNAPP_API_KEY"],
|
||||
CoinBinanceAPIURL: envMap["COIN_BINANCE_API_URL"],
|
||||
CoinCoinbaseAPIURL: envMap["COIN_COINBASE_API_URL"],
|
||||
CoinCoinGeckoAPIURL: envMap["COIN_COINGECKO_API_URL"],
|
||||
Modules: splitCSV(envMap["MODULES"]),
|
||||
BotOwnerID: parseInt64(envMap["OWNER_ID"]),
|
||||
AdminUserIDs: parseInt64Set(envMap["ADMIN_IDS"]),
|
||||
KVProvider: envMap["KV_PROVIDER"],
|
||||
MongoURL: envMap["MONGO_URL"],
|
||||
MongoDatabase: envMap["MONGO_DATABASE"],
|
||||
}
|
||||
}
|
||||
|
||||
func exportOptionalEnv(key, value string) {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return
|
||||
}
|
||||
if err := os.Setenv(key, value); err != nil {
|
||||
log.Warn("could not export optional env", "key", key, "err", err)
|
||||
Port: port,
|
||||
TelegramBotToken: envMap["TELEGRAM_BOT_TOKEN"],
|
||||
SourceCommit: envMap["SOURCE_COMMIT"],
|
||||
GeminiAPIKey: envMap["GEMINI_API_KEY"],
|
||||
Modules: splitCSV(envMap["MODULES"]),
|
||||
BotOwnerID: parseInt64(envMap["OWNER_ID"]),
|
||||
AdminUserIDs: parseInt64Set(envMap["ADMIN_IDS"]),
|
||||
KVProvider: envMap["KV_PROVIDER"],
|
||||
MongoURL: envMap["MONGO_URL"],
|
||||
MongoDatabase: envMap["MONGO_DATABASE"],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -24,9 +24,10 @@ services:
|
||||
# PORT defaults to 8080 (internal health server) — omit unless overriding.
|
||||
# Long polling = no TELEGRAM_WEBHOOK_SECRET, no /webhook, no public domain.
|
||||
# Cron is in-process only — there is no /cron HTTP route and no secret.
|
||||
# No stock/coin/gold *_API_URL overrides — modules use their coded default
|
||||
# providers (stock: SSI/VCI/KBS; coin: Binance→Coinbase→CoinGecko;
|
||||
# gold: VNAppMob→spot). GOLD_VNAPP_API_KEY auto-fetches + caches to Mongo.
|
||||
# No stock/coin/gold URL env overrides — modules use their coded default
|
||||
# providers (stock: SSI/VCI/KBS; coin: Binance->Coinbase->CoinGecko;
|
||||
# gold: VNAppMob). If GOLD_VNAPP_API_KEY is unset, the bot auto-fetches
|
||||
# and caches a key to Mongo.
|
||||
# Long polling is outbound-only: nothing inbound to route, so no published
|
||||
# ports and no public domain. `expose` keeps :8080 reachable inside the
|
||||
# Coolify network for the container health monitor against GET / only.
|
||||
|
||||
@@ -37,8 +37,8 @@ Copy [`.env.example`](../.env.example) → `.env` (gitignored) and fill in.
|
||||
| `WC_FOOTBALL_DATA_TOKEN` | optional | football-data.org token for the `wc` module |
|
||||
|
||||
**Leave UNSET on self-host:** `KV_PROVIDER`, `PORT`,
|
||||
`TELEGRAM_WEBHOOK_SECRET`, `GOLD_VNAPP_API_KEY`, and the `STOCK/COIN/GOLD
|
||||
*_API_URL` overrides (modules use coded defaults).
|
||||
`TELEGRAM_WEBHOOK_SECRET`, and `GOLD_VNAPP_API_KEY`. Stock, coin, and gold URL
|
||||
overrides are not supported in runtime env; modules use coded defaults.
|
||||
|
||||
> Cron runs in-process (`internal/cron`) — there is no `/cron` HTTP route and no
|
||||
> `CRON_SHARED_SECRET`. The scheduler is the sole trigger; nothing inbound.
|
||||
|
||||
@@ -28,7 +28,7 @@ type state struct {
|
||||
}
|
||||
|
||||
func newState(store Store) *state {
|
||||
return &state{store: store, prices: NewPriceClientFromEnv()}
|
||||
return &state{store: store, prices: NewPriceClient()}
|
||||
}
|
||||
|
||||
func (s *state) now() time.Time {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -47,13 +46,13 @@ type cachedPrice struct {
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
func NewPriceClientFromEnv() *PriceClient {
|
||||
func NewPriceClient() *PriceClient {
|
||||
httpClient := &http.Client{Timeout: coinHTTPTimeout}
|
||||
return &PriceClient{
|
||||
Providers: []PriceProvider{
|
||||
&BinanceProvider{HTTP: httpClient, URL: os.Getenv("COIN_BINANCE_API_URL")},
|
||||
&CoinbaseProvider{HTTP: httpClient, URL: os.Getenv("COIN_COINBASE_API_URL")},
|
||||
&CoinGeckoProvider{HTTP: httpClient, URL: os.Getenv("COIN_COINGECKO_API_URL")},
|
||||
&BinanceProvider{HTTP: httpClient},
|
||||
&CoinbaseProvider{HTTP: httpClient},
|
||||
&CoinGeckoProvider{HTTP: httpClient},
|
||||
},
|
||||
CacheTTL: coinPriceCacheTTL,
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ type compositePriceFetcher struct {
|
||||
vnappmob sjcPriceFetcher
|
||||
}
|
||||
|
||||
// NewCompositePriceFetcherFromEnv builds the production price fetcher using
|
||||
// only the env-driven VNAppMob client.
|
||||
func NewCompositePriceFetcherFromEnv(coll storage.Collection) priceFetcher {
|
||||
// NewCompositePriceFetcher builds the production price fetcher using the coded
|
||||
// VNAppMob endpoint.
|
||||
func NewCompositePriceFetcher(coll storage.Collection) priceFetcher {
|
||||
return &compositePriceFetcher{
|
||||
vnappmob: NewVNAppMobClientFromEnv(coll),
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ func TestGoldPriceLines(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCompositePriceFetcherFromEnv(t *testing.T) {
|
||||
f, ok := NewCompositePriceFetcherFromEnv(storage.NewMemoryProvider().Collection("gold")).(*compositePriceFetcher)
|
||||
func TestNewCompositePriceFetcher(t *testing.T) {
|
||||
f, ok := NewCompositePriceFetcher(storage.NewMemoryProvider().Collection("gold")).(*compositePriceFetcher)
|
||||
if !ok {
|
||||
t.Fatalf("expected *compositePriceFetcher, got %T", f)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ type state struct {
|
||||
func newState(coll storage.Collection) *state {
|
||||
return &state{
|
||||
store: storage.Typed[Portfolio](coll),
|
||||
prices: NewCompositePriceFetcherFromEnv(coll),
|
||||
prices: NewCompositePriceFetcher(coll),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -42,7 +41,8 @@ type SJCPrice struct {
|
||||
type GoldPriceClient struct {
|
||||
HTTP *http.Client
|
||||
|
||||
// Per-provider URL overrides; empty means the provider default.
|
||||
// URL fields are explicit injection points for tests; production uses the
|
||||
// provider defaults.
|
||||
GoldURL string // primary: gold-api.com
|
||||
SwissquoteURL string
|
||||
NBPURL string
|
||||
@@ -57,13 +57,6 @@ type GoldPriceClient struct {
|
||||
fxExpiry time.Time
|
||||
}
|
||||
|
||||
func NewGoldPriceClientFromEnv() *GoldPriceClient {
|
||||
return &GoldPriceClient{
|
||||
GoldURL: strings.TrimSpace(os.Getenv("GOLD_PRICE_API_URL")),
|
||||
FXURL: strings.TrimSpace(os.Getenv("GOLD_FX_API_URL")),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GoldPriceClient) FetchPrice(ctx context.Context) (GoldPrice, error) {
|
||||
xauUSD, err := c.fetchXAUUSD(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,7 +36,7 @@ type apiKeyCache struct {
|
||||
// refreshing it before expiry or when the SJC endpoint returns 403.
|
||||
type VNAppMobClient struct {
|
||||
HTTP *http.Client
|
||||
BaseURL string // optional override; default https://api.vnappmob.com
|
||||
BaseURL string // explicit test override; production uses https://api.vnappmob.com
|
||||
Token string // optional env override (GOLD_VNAPP_API_KEY)
|
||||
cache storage.DocStore[apiKeyCache] // module-scoped typed cache store
|
||||
|
||||
@@ -44,14 +44,13 @@ type VNAppMobClient struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewVNAppMobClientFromEnv creates a client reading GOLD_VNAPP_API_URL and
|
||||
// GOLD_VNAPP_API_KEY from the environment. When the key is empty, the client
|
||||
// refreshes it automatically via the VNAppMob refresh endpoint.
|
||||
// NewVNAppMobClientFromEnv creates a client reading only GOLD_VNAPP_API_KEY
|
||||
// from the environment. When the key is empty, the client refreshes it
|
||||
// automatically via the coded VNAppMob endpoint.
|
||||
func NewVNAppMobClientFromEnv(coll storage.Collection) *VNAppMobClient {
|
||||
return &VNAppMobClient{
|
||||
BaseURL: strings.TrimSpace(os.Getenv("GOLD_VNAPP_API_URL")),
|
||||
Token: strings.TrimSpace(os.Getenv("GOLD_VNAPP_API_KEY")),
|
||||
cache: storage.Typed[apiKeyCache](coll),
|
||||
Token: strings.TrimSpace(os.Getenv("GOLD_VNAPP_API_KEY")),
|
||||
cache: storage.Typed[apiKeyCache](coll),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user