Files
boc/aamos-admin-upgrade/backend/main.go
T
Bernt 6de2455917 v1.2.0: Add Global Markets footer, translated to 9 languages
- Added GLOBAL_MARKETS_TITLE to all translation files
- Updated footer with 12 markets (4 active + 8 upcoming)
- Translated market section to: zh-cn, zh-tw, ja, ko, th, vi, id, ms, hi
- Built and deployed to production
- CloudFront invalidation: I3RTMXVFDJXWLG3SYX208OP1CC
2026-07-08 19:56:03 +00:00

137 lines
3.7 KiB
Go

package main
import (
"context"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-chi/chi/v5"
chimw "github.com/go-chi/chi/v5/middleware"
"github.com/rs/zerolog"
"github.com/rs/zerolog/hlog"
"aamos-admin/config"
"aamos-admin/db"
"aamos-admin/handlers"
"aamos-admin/middleware"
)
func main() {
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
cfg := config.Load()
port := os.Getenv("PORT")
if port == "" {
port = "9090"
}
database, err := db.Connect(cfg.DBURL)
if err != nil {
logger.Fatal().Err(err).Msg("database connect failed")
}
defer database.Close()
// Extend users table for password auth and soft-delete (idempotent).
if _, err := database.Exec(
`ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash TEXT NOT NULL DEFAULT ''`,
); err != nil {
logger.Fatal().Err(err).Msg("password_hash migration failed")
}
usersH := &handlers.UsersHandler{DB: database}
if err := usersH.EnsureSchema(); err != nil {
logger.Fatal().Err(err).Msg("users deleted_at migration failed")
}
authH := &handlers.AuthHandler{
DB: database,
JWTSecret: []byte(cfg.JWTSecret),
}
// External data handler (quiXzoom, LandveX, Economy, CRM)
externalH := handlers.UnifiedDashboardHandler(database, cfg.AMOSBaseURL)
modulesH, err := handlers.NewModulesHandler(database)
if err != nil {
logger.Fatal().Err(err).Msg("modules seed failed")
}
settingsH := handlers.NewSettingsHandler(database)
if err := settingsH.Init(); err != nil {
logger.Fatal().Err(err).Msg("settings init failed")
}
onboardingH := handlers.NewOnboardingHandler(database)
if err := onboardingH.Init(); err != nil {
logger.Fatal().Err(err).Msg("onboarding init failed")
}
hub := handlers.NewHub()
go hub.Run()
r := chi.NewRouter()
r.Use(middleware.CORS)
r.Use(hlog.NewHandler(logger))
r.Use(hlog.RequestIDHandler("req_id", "X-Request-ID"))
r.Use(middleware.Logger(logger))
r.Use(chimw.Recoverer)
// Public
r.Handle("/health", handlers.NewHealthHandler())
r.Post("/api/v1/auth/login", authH.Login)
r.Post("/api/v1/auth/logout", authH.Logout)
// Protected
r.Group(func(r chi.Router) {
r.Use(middleware.Auth(cfg))
r.Get("/api/v1/auth/me", authH.Me)
r.Get("/api/v1/dashboard", handlers.DashboardHandler(database))
r.Get("/api/v1/unified-dashboard", externalH)
r.Get("/api/v1/service-health", handlers.ServiceHealthHandler)
r.Get("/api/v1/metrics", handlers.MetricsHandler)
r.Get("/api/v1/audit", handlers.AuditHandler(database))
// UsersHandler.ServeHTTP dispatches on method and path suffix.
r.Handle("/api/v1/users", usersH)
r.Handle("/api/v1/users/{id}", usersH)
// ModulesHandler methods; Toggle parses the path internally.
r.Get("/api/v1/modules", modulesH.List)
r.Put("/api/v1/modules/{id}/toggle", modulesH.Toggle)
// SettingsHandler.ServeHTTP dispatches on GET/PUT.
r.Handle("/api/v1/settings", settingsH)
r.Post("/api/v1/onboarding/start", onboardingH.ServeHTTP)
// WebSocket — upgrade is rejected before handshake if token is missing.
r.Get("/ws/metrics", hub.ServeWS)
})
srv := &http.Server{
Addr: ":" + port,
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
logger.Info().Str("addr", srv.Addr).Msg("server starting")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatal().Err(err).Msg("listen error")
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Info().Msg("shutting down")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
logger.Error().Err(err).Msg("shutdown error")
}
}