mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
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:
@@ -0,0 +1,153 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/assimon/luuu/model/data"
|
||||
"github.com/assimon/luuu/model/mdb"
|
||||
"github.com/assimon/luuu/telegram"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// SettingUpsertItem is a single setting entry for batch upsert.
|
||||
// Supported groups and keys:
|
||||
//
|
||||
// - group=rate:
|
||||
// rate.forced_usdt_rate (float) — override USDT exchange rate (0 = use api)
|
||||
// rate.api_url (string) — external rate API URL
|
||||
// rate.adjust_percent (float) — rate adjustment percentage
|
||||
// rate.okx_c2c_enabled (bool) — use OKX C2C rate feed
|
||||
//
|
||||
// - group=epay:
|
||||
// epay.default_token (string) — token for EPAY orders, e.g. "usdt" (default)
|
||||
// epay.default_currency (string) — fiat currency for EPAY orders, e.g. "cny" (default)
|
||||
// epay.default_network (string) — blockchain network for EPAY orders, e.g. "tron" (default)
|
||||
//
|
||||
// - group=brand:
|
||||
// brand.site_name (string) — site display name
|
||||
// brand.logo_url (string) — logo image URL
|
||||
// brand.page_title (string) — payment page title
|
||||
// brand.pay_success_text (string) — text shown on payment success
|
||||
// brand.support_url (string) — support / help URL
|
||||
//
|
||||
// - group=system:
|
||||
// system.order_expiration_time (int) — order expiry in minutes
|
||||
type SettingUpsertItem struct {
|
||||
Group string `json:"group" enums:"brand,rate,system,epay" example:"epay"`
|
||||
Key string `json:"key" example:"epay.default_network"`
|
||||
Value string `json:"value" example:"tron"`
|
||||
Type string `json:"type" enums:"string,int,bool,json" example:"string"`
|
||||
}
|
||||
|
||||
// SettingsUpsertRequest is the payload for batch upserting settings.
|
||||
type SettingsUpsertRequest struct {
|
||||
Items []SettingUpsertItem `json:"items" validate:"required"`
|
||||
}
|
||||
|
||||
// ListSettings returns all rows, optionally filtered by group.
|
||||
// @Summary List settings
|
||||
// @Description Returns all settings, optionally filtered by group.
|
||||
// @Description Available groups: brand, rate, system, epay.
|
||||
// @Description See SettingUpsertItem for the full list of supported keys per group.
|
||||
// @Tags Admin Settings
|
||||
// @Security AdminJWT
|
||||
// @Produce json
|
||||
// @Param group query string false "Group filter (brand|rate|system|epay)"
|
||||
// @Success 200 {object} response.ApiResponse{data=[]mdb.Setting}
|
||||
// @Failure 400 {object} response.ApiResponse
|
||||
// @Router /admin/api/v1/settings [get]
|
||||
func (c *BaseAdminController) ListSettings(ctx echo.Context) error {
|
||||
group := strings.ToLower(strings.TrimSpace(ctx.QueryParam("group")))
|
||||
rows, err := data.ListSettingsByGroup(group)
|
||||
if err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
return c.SucJson(ctx, rows)
|
||||
}
|
||||
|
||||
// UpsertSettings batch-inserts / updates rows. Each item is treated
|
||||
// independently so a malformed row in the middle doesn't drop earlier
|
||||
// ones. Errors are returned per-key so the UI can surface them.
|
||||
// @Summary Upsert settings
|
||||
// @Description Batch insert/update settings. Returns per-key status.
|
||||
// @Description Supported groups: brand, rate, system, epay.
|
||||
// @Description epay group keys: epay.default_token (e.g. "usdt"), epay.default_currency (e.g. "cny"), epay.default_network (e.g. "tron").
|
||||
// @Description rate group keys: rate.forced_usdt_rate, rate.api_url, rate.adjust_percent, rate.okx_c2c_enabled.
|
||||
// @Description brand group keys: brand.site_name, brand.logo_url, brand.page_title, brand.pay_success_text, brand.support_url.
|
||||
// @Description system group keys: system.order_expiration_time.
|
||||
// @Tags Admin Settings
|
||||
// @Security AdminJWT
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body admin.SettingsUpsertRequest true "Settings payload"
|
||||
// @Success 200 {object} response.ApiResponse{data=[]admin.SettingsUpsertResult}
|
||||
// @Failure 400 {object} response.ApiResponse
|
||||
// @Router /admin/api/v1/settings [put]
|
||||
func (c *BaseAdminController) UpsertSettings(ctx echo.Context) error {
|
||||
req := new(SettingsUpsertRequest)
|
||||
if err := ctx.Bind(req); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
if err := c.ValidateStruct(ctx, req); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
type result struct {
|
||||
Key string `json:"key"`
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
out := make([]result, 0, len(req.Items))
|
||||
for _, item := range req.Items {
|
||||
key := strings.TrimSpace(item.Key)
|
||||
if key == "" {
|
||||
out = append(out, result{Key: item.Key, OK: false, Error: "key required"})
|
||||
continue
|
||||
}
|
||||
if err := data.SetSetting(item.Group, key, item.Value, item.Type); err != nil {
|
||||
out = append(out, result{Key: key, OK: false, Error: err.Error()})
|
||||
continue
|
||||
}
|
||||
out = append(out, result{Key: key, OK: true})
|
||||
}
|
||||
|
||||
// When telegram credentials are updated via settings, reload the
|
||||
// command bot so operators don't need to restart the process.
|
||||
telegramKeys := map[string]bool{
|
||||
"system.telegram_bot_token": true,
|
||||
"system.telegram_chat_id": true,
|
||||
}
|
||||
for _, item := range req.Items {
|
||||
if telegramKeys[strings.TrimSpace(item.Key)] {
|
||||
telegram.ReloadBotAsync("settings upsert")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return c.SucJson(ctx, out)
|
||||
}
|
||||
|
||||
// DeleteSetting removes one row. The next read of that key will fall
|
||||
// back to the hardcoded default (see settings_data.GetSetting*).
|
||||
// @Summary Delete setting
|
||||
// @Description Remove a setting by key (falls back to default)
|
||||
// @Tags Admin Settings
|
||||
// @Security AdminJWT
|
||||
// @Produce json
|
||||
// @Param key path string true "Setting key"
|
||||
// @Success 200 {object} response.ApiResponse
|
||||
// @Failure 400 {object} response.ApiResponse
|
||||
// @Router /admin/api/v1/settings/{key} [delete]
|
||||
func (c *BaseAdminController) DeleteSetting(ctx echo.Context) error {
|
||||
key := strings.TrimSpace(ctx.Param("key"))
|
||||
if key == "" {
|
||||
return c.SucJson(ctx, nil)
|
||||
}
|
||||
if err := data.DeleteSetting(key); err != nil {
|
||||
return c.FailJson(ctx, err)
|
||||
}
|
||||
return c.SucJson(ctx, nil)
|
||||
}
|
||||
|
||||
// Public helper for the rate/usdt overrides — used by config package to
|
||||
// read settings-backed values without importing the controller package.
|
||||
var _ = mdb.SettingKeyRateForcedUsdt // ensure key constants remain referenced
|
||||
Reference in New Issue
Block a user