feat: add admin panel with multi-chain support and notification system

- Add full admin REST API: auth, API keys, chains, chain tokens, wallets,
  orders, RPC nodes, settings, dashboard stats, notifications
- Add JWT-based admin authentication and API key auth middleware
- Add multi-chain listeners: BSC, ETH, Polygon, Plasma, EVM WebSocket
- Add RPC node health check job with automatic failover
- Add Telegram notification channel for order events
- Add installer for first-run database initialization
- Add new DB models: admin_user, api_key, chain, chain_token,
  rpc_node, settings, notification_channel
- Add order statistics aggregation (daily/monthly/by-chain)
- Serve built admin SPA from embedded www/ assets
- Add comprehensive test coverage for all new features
This commit is contained in:
line-6000
2026-04-22 02:28:31 +08:00
parent 097c716714
commit 6bb47d4b00
247 changed files with 9933 additions and 1279 deletions
+68
View File
@@ -0,0 +1,68 @@
package http
import (
"path/filepath"
"testing"
)
func TestResolveSPAFilePath(t *testing.T) {
root := filepath.Join("tmp", "www")
indexPath := filepath.Join(root, "index.html")
tests := []struct {
name string
wildcard string
wantPath string
wantTryStat bool
}{
{
name: "relative asset path",
wildcard: "assets/app.js",
wantPath: filepath.Join(root, "assets", "app.js"),
wantTryStat: true,
},
{
name: "absolute style asset path",
wildcard: "/assets/app.js",
wantPath: filepath.Join(root, "assets", "app.js"),
wantTryStat: true,
},
{
name: "empty wildcard",
wildcard: "",
wantPath: indexPath,
wantTryStat: false,
},
{
name: "dot wildcard",
wildcard: ".",
wantPath: indexPath,
wantTryStat: false,
},
{
name: "directory traversal fallback",
wildcard: "../../etc/passwd",
wantPath: indexPath,
wantTryStat: false,
},
{
name: "absolute directory traversal fallback",
wildcard: "/../../etc/passwd",
wantPath: filepath.Join(root, "etc", "passwd"),
wantTryStat: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPath, gotTryStat := ResolveSPAFilePath(root, tt.wildcard)
if gotTryStat != tt.wantTryStat {
t.Fatalf("tryStat = %v, want %v", gotTryStat, tt.wantTryStat)
}
if gotPath != tt.wantPath {
t.Fatalf("path = %q, want %q", gotPath, tt.wantPath)
}
})
}
}