Hardening pass over the API surface. No route's auth requirements change.
Resilience:
- Add recover middleware. There was none, so an unhandled panic in any
handler propagated out of the process instead of becoming a 500.
- Add a centralized ErrorHandler so errors and recovered panics return the
same {success,message} envelope as the utils helpers, not Fiber's default
plain-text body. 5xx responses are logged with method and path.
Rate limiting:
- Global 300/min per IP as an abuse backstop, exempting health/readiness
probes and websocket upgrades.
- 10/min shared across every credential endpoint (customer/miler/admin/hub
login, verify-pin, reset-pin, email OTP). PINs are 4 digits, so the whole
keyspace was previously walkable in seconds. One shared limiter instance
means rotating between endpoints doesn't reset the budget.
- Add TRUSTED_PROXIES config. Limits key on c.IP(), which behind a TLS
terminator is the proxy, collapsing every client into one bucket. When set,
X-Forwarded-For is honoured only from those proxies so the header can't be
spoofed to dodge the limit. Logs a warning when unset.
Transactions:
- Check the error on all 51 previously-unchecked tx.Save/Create/Delete/
Model(...).Update/Commit calls across 6 controllers. A failed write inside
a transaction was silently ignored and the request still reported success;
an unchecked Commit could fail with the caller told everything worked.
Each site now rolls back and returns a specific message.
Pagination:
- Add utils.ParsePage/Paginated, reusing the pageno/pagesize convention
GetAdminBookings already established. Default 500, hard cap 1000.
- Apply to the previously unbounded consignments, tripsheets, exceptions,
app-users and clients endpoints. Defaults are high so existing consoles
that don't paginate keep working; the cap only stops a growing table from
being loaded wholesale. total is now a real COUNT, not len(data).
- GetClients also loaded the entire auth table to join in memory; it now
fetches only the current page's rows.
Tests (first in the repo):
- Extract the hyperlocal pincode rule out of BookingPickupComplete into
isHyperlocal so it is testable, covering the short/empty pincode fallback.
- Cover calculateVolumetricWeight and the ParsePage clamping rules.
Repo hygiene:
- Tag scratch/*.go with //go:build ignore. Each declared its own main(), so
`go build ./...` failed on redeclaration; it now passes repo-wide.
- Untrack scratch/node_modules (216 files) and ignore node_modules, test
artifacts, and the `doormile` binary `go build .` emits.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
71 lines
2.2 KiB
Go
71 lines
2.2 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
)
|
|
|
|
type Config struct {
|
|
Env string
|
|
Port string
|
|
DBName string
|
|
DBUser string
|
|
DBPassword string
|
|
DBPort string
|
|
DBHost string
|
|
RedisHost string
|
|
RedisPort string
|
|
RedisUser string
|
|
RedisPassword string
|
|
JWTSecret string
|
|
NatsURL string
|
|
NatsUser string
|
|
NatsPassword string
|
|
AILayerBaseURL string // AI decision-engine service base URL (e.g. http://rider-api:8082)
|
|
|
|
// TrustedProxies is a comma-separated list of reverse-proxy IPs/CIDRs that
|
|
// are allowed to set X-Forwarded-For. Rate limiting keys on the client IP,
|
|
// so behind a proxy this MUST be set — otherwise every request appears to
|
|
// come from the proxy and the whole fleet shares one limit bucket.
|
|
// Empty means "no proxy": the socket peer address is used as-is.
|
|
TrustedProxies string
|
|
SMTPHost string
|
|
SMTPPort string
|
|
SMTPUser string
|
|
SMTPPassword string
|
|
SMTPFrom string
|
|
}
|
|
|
|
func Load() *Config {
|
|
return &Config{
|
|
Env: getEnv("ENV", "development"),
|
|
Port: getEnv("APP_PORT", "8081"),
|
|
DBName: getEnv("DB_NAME", "logistics"),
|
|
DBUser: getEnv("DB_USER", "admin"),
|
|
DBPassword: getEnv("DB_PASSWORD", "Package@321#"),
|
|
DBPort: getEnv("DB_PORT", "5433"),
|
|
DBHost: getEnv("DB_HOST", "127.0.0.1"),
|
|
RedisHost: getEnv("REDIS_HOST", "127.0.0.1"),
|
|
RedisPort: getEnv("REDIS_PORT", "6379"),
|
|
RedisUser: getEnv("REDIS_USER", ""),
|
|
RedisPassword: getEnv("REDIS_PASSWORD", ""),
|
|
JWTSecret: getEnv("JWT_SECRET_KEY", "DoormileSuperSecretJWTKey2026!"),
|
|
NatsURL: getEnv("NATS_URL", "nats://66.116.226.161:4223"),
|
|
NatsUser: getEnv("NATS_USER", "doormile"),
|
|
NatsPassword: getEnv("NATS_PASSWORD", "Package@321#"),
|
|
AILayerBaseURL: getEnv("AI_LAYER_BASE_URL", "https://routemate.workolik.com"),
|
|
TrustedProxies: getEnv("TRUSTED_PROXIES", ""),
|
|
SMTPHost: getEnv("SMTP_HOST", ""),
|
|
SMTPPort: getEnv("SMTP_PORT", "465"),
|
|
SMTPUser: getEnv("SMTP_USER", ""),
|
|
SMTPPassword: getEnv("SMTP_PASSWORD", ""),
|
|
SMTPFrom: getEnv("SMTP_FROM", ""),
|
|
}
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|