Files
iqudo-top1/main.go
2026-08-18 16:27:02 +08:00

110 lines
3.1 KiB
Go

package main
import (
"context"
"embed"
"flag"
"fmt"
"io/fs"
"net/http"
"os"
"os/signal"
"syscall"
"git.iwork-ai.com/xdc/iqudo-top1/internal/auth"
"git.iwork-ai.com/xdc/iqudo-top1/internal/config"
"git.iwork-ai.com/xdc/iqudo-top1/internal/database"
"git.iwork-ai.com/xdc/iqudo-top1/internal/httpserver"
"git.iwork-ai.com/xdc/iqudo-top1/internal/logger"
"git.iwork-ai.com/xdc/iqudo-top1/internal/migration"
"git.iwork-ai.com/xdc/iqudo-top1/internal/multitable"
"go.uber.org/zap"
)
//go:embed migrations/*.sql
var migrationFiles embed.FS
//go:embed web/dist/*
var frontendFiles embed.FS
//go:embed sdk/dist/*
var sdkFiles embed.FS
func main() {
environment := flag.String("env", "", "runtime environment: development, test, prod")
configDir := flag.String("config-dir", "configs", "configuration directory")
flag.Parse()
cfg, err := config.Load(config.LoadOptions{Environment: *environment, ConfigDir: *configDir})
if err != nil {
fmt.Fprintf(os.Stderr, "load configuration: %v\n", err)
os.Exit(1)
}
log, err := logger.New(cfg.App.Env, cfg.App.Name)
if err != nil {
fmt.Fprintf(os.Stderr, "create logger: %v\n", err)
os.Exit(1)
}
defer log.Sync()
undoGlobalLogger := zap.ReplaceGlobals(log.Named("business"))
defer undoGlobalLogger()
db, err := database.Open(cfg.Database, log)
if err != nil {
log.Fatal("connect database", zap.Error(err))
}
if err := migration.Run(db, migrationFiles); err != nil {
log.Fatal("run database migrations", zap.Error(err))
}
if err := auth.Seed(db, cfg.Seed); err != nil {
log.Fatal("seed administrator", zap.Error(err))
}
workerDone := make(chan struct{})
workerCtx, stopWorker := context.WithCancel(context.Background())
if cfg.MultiTable.Enabled {
worker := multitable.NewWorker(db, cfg.MultiTable, log)
go func() {
defer close(workerDone)
log.Info("multitable synchronization started", zap.String("worker", worker.String()))
worker.Run(workerCtx)
}()
} else {
close(workerDone)
}
frontend, err := fs.Sub(frontendFiles, "web/dist")
if err != nil {
log.Fatal("load embedded frontend", zap.Error(err))
}
sdk, err := fs.Sub(sdkFiles, "sdk/dist")
if err != nil {
log.Fatal("load embedded sdk", zap.Error(err))
}
authService := auth.NewService(db, cfg.Auth)
handler := httpserver.New(db, authService, frontend, sdk, log, cfg.App.Env)
server := &http.Server{Addr: cfg.Server.Address(), Handler: handler, ReadTimeout: cfg.Server.ReadTimeout, WriteTimeout: cfg.Server.WriteTimeout}
go func() {
log.Info("server started", zap.String("address", cfg.Server.Address()))
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("server stopped unexpectedly", zap.Error(err))
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
stopWorker()
ctx, cancel := context.WithTimeout(context.Background(), cfg.Server.ShutdownTimeout)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Error("graceful shutdown failed", zap.Error(err))
}
select {
case <-workerDone:
case <-ctx.Done():
log.Warn("multitable worker did not stop before shutdown timeout")
}
log.Info("server stopped")
}