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:
+99
@@ -1,11 +1,110 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/assimon/luuu/command"
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
//go:embed all:static
|
||||
var staticDir embed.FS
|
||||
|
||||
//go:embed all:www
|
||||
var wwwDir embed.FS
|
||||
|
||||
func executableDir() (string, error) {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
exePath, err = filepath.EvalSymlinks(exePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Dir(exePath), nil
|
||||
}
|
||||
|
||||
func extractEmbeddedDir(src embed.FS, embeddedRoot, dstRoot string) error {
|
||||
sub, err := fs.Sub(src, embeddedRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fs.WalkDir(sub, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dstRoot, path)
|
||||
|
||||
if d.IsDir() {
|
||||
return os.MkdirAll(dstPath, 0o755)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in, err := sub.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func releaseStatic(fs embed.FS, target string) (string, error) {
|
||||
baseDir, err := executableDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
targetDir := filepath.Join(baseDir, target)
|
||||
|
||||
if err := os.RemoveAll(targetDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(targetDir, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := extractEmbeddedDir(fs, target, targetDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return targetDir, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
staticPath, err := releaseStatic(staticDir, "static")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("static released to:", staticPath)
|
||||
|
||||
wwwwPath, err := releaseStatic(wwwDir, "www")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("www released to:", wwwwPath)
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
color.Error.Println("[Start Server Err!!!] ", err)
|
||||
|
||||
Reference in New Issue
Block a user