|
| 1 | +package main |
| 2 | + |
| 3 | +// @title Acontext Admin API |
| 4 | +// @version 1.0 |
| 5 | +// @description API for Acontext Admin. |
| 6 | +// @schemes http https |
| 7 | +// @BasePath / |
| 8 | + |
| 9 | +// Bearer at Project level |
| 10 | +// @securityDefinitions.apikey BearerAuth |
| 11 | +// @in header |
| 12 | +// @name Authorization |
| 13 | +// @description Dynamically generate a signature for admin-level authentication |
| 14 | + |
| 15 | +import ( |
| 16 | + "context" |
| 17 | + "fmt" |
| 18 | + "net/http" |
| 19 | + "os" |
| 20 | + "os/signal" |
| 21 | + "syscall" |
| 22 | + "time" |
| 23 | + |
| 24 | + "github.com/gin-gonic/gin" |
| 25 | + "github.com/memodb-io/Acontext/internal/bootstrap" |
| 26 | + "github.com/memodb-io/Acontext/internal/config" |
| 27 | + "github.com/memodb-io/Acontext/internal/infra/cache" |
| 28 | + dbpkg "github.com/memodb-io/Acontext/internal/infra/db" |
| 29 | + "github.com/memodb-io/Acontext/internal/modules/handler" |
| 30 | + "github.com/memodb-io/Acontext/internal/pkg/tokenizer" |
| 31 | + "github.com/memodb-io/Acontext/internal/router" |
| 32 | + "github.com/memodb-io/Acontext/internal/telemetry" |
| 33 | + "github.com/redis/go-redis/v9" |
| 34 | + "github.com/samber/do" |
| 35 | + "go.uber.org/zap" |
| 36 | + "gorm.io/gorm" |
| 37 | +) |
| 38 | + |
| 39 | +func main() { |
| 40 | + // build dependency injection container (admin = base + admin deps) |
| 41 | + inj := bootstrap.BuildAdminContainer() |
| 42 | + |
| 43 | + cfg := do.MustInvoke[*config.Config](inj) |
| 44 | + log := do.MustInvoke[*zap.Logger](inj) |
| 45 | + db := do.MustInvoke[*gorm.DB](inj) |
| 46 | + rdb := do.MustInvoke[*redis.Client](inj) |
| 47 | + |
| 48 | + // Initialize tokenizer (vocabulary is already embedded in the package) |
| 49 | + if err := tokenizer.Init(log); err != nil { |
| 50 | + log.Sugar().Fatalw("failed to initialize tokenizer", "err", err) |
| 51 | + } |
| 52 | + |
| 53 | + // Setup OpenTelemetry tracing (using configuration system) |
| 54 | + tp, err := telemetry.SetupTracing(cfg) |
| 55 | + if err != nil { |
| 56 | + log.Sugar().Warnw("failed to setup tracing, continuing without tracing", "err", err) |
| 57 | + } else if tp != nil { |
| 58 | + log.Sugar().Info("OpenTelemetry tracing enabled", "endpoint", cfg.Telemetry.OtlpEndpoint) |
| 59 | + defer func() { |
| 60 | + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
| 61 | + defer cancel() |
| 62 | + if err := telemetry.Shutdown(ctx); err != nil { |
| 63 | + log.Sugar().Errorw("failed to shutdown tracer", "err", err) |
| 64 | + } |
| 65 | + }() |
| 66 | + |
| 67 | + // Register GORM OpenTelemetry plugin after tracer provider is set |
| 68 | + if err := dbpkg.RegisterOpenTelemetryPlugin(db); err != nil { |
| 69 | + log.Sugar().Warnw("failed to register GORM OpenTelemetry plugin, continuing without database tracing", "err", err) |
| 70 | + } else { |
| 71 | + log.Sugar().Info("GORM OpenTelemetry plugin registered") |
| 72 | + } |
| 73 | + |
| 74 | + // Register Redis OpenTelemetry plugin after tracer provider is set |
| 75 | + if err := cache.RegisterOpenTelemetryPlugin(rdb); err != nil { |
| 76 | + log.Sugar().Warnw("failed to register Redis OpenTelemetry plugin, continuing without Redis tracing", "err", err) |
| 77 | + } else { |
| 78 | + log.Sugar().Info("Redis OpenTelemetry plugin registered") |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + // init gin |
| 83 | + gin.SetMode(cfg.App.Env) |
| 84 | + |
| 85 | + // build base handlers |
| 86 | + sessionHandler := do.MustInvoke[*handler.SessionHandler](inj) |
| 87 | + diskHandler := do.MustInvoke[*handler.DiskHandler](inj) |
| 88 | + artifactHandler := do.MustInvoke[*handler.ArtifactHandler](inj) |
| 89 | + taskHandler := do.MustInvoke[*handler.TaskHandler](inj) |
| 90 | + agentSkillsHandler := do.MustInvoke[*handler.AgentSkillsHandler](inj) |
| 91 | + userHandler := do.MustInvoke[*handler.UserHandler](inj) |
| 92 | + sandboxHandler := do.MustInvoke[*handler.SandboxHandler](inj) |
| 93 | + learningSpaceHandler := do.MustInvoke[*handler.LearningSpaceHandler](inj) |
| 94 | + sessionEventHandler := do.MustInvoke[*handler.SessionEventHandler](inj) |
| 95 | + projectHandler := do.MustInvoke[*handler.ProjectHandler](inj) |
| 96 | + |
| 97 | + // build admin-specific handlers |
| 98 | + adminHandler := do.MustInvoke[*handler.AdminHandler](inj) |
| 99 | + metricsHandler := do.MustInvoke[*handler.MetricsHandler](inj) |
| 100 | + |
| 101 | + engine := router.NewAdminRouter(router.AdminRouterDeps{ |
| 102 | + RouterDeps: router.RouterDeps{ |
| 103 | + Config: cfg, |
| 104 | + DB: db, |
| 105 | + Log: log, |
| 106 | + SessionHandler: sessionHandler, |
| 107 | + DiskHandler: diskHandler, |
| 108 | + ArtifactHandler: artifactHandler, |
| 109 | + TaskHandler: taskHandler, |
| 110 | + AgentSkillsHandler: agentSkillsHandler, |
| 111 | + UserHandler: userHandler, |
| 112 | + SandboxHandler: sandboxHandler, |
| 113 | + LearningSpaceHandler: learningSpaceHandler, |
| 114 | + SessionEventHandler: sessionEventHandler, |
| 115 | + ProjectHandler: projectHandler, |
| 116 | + }, |
| 117 | + AdminHandler: adminHandler, |
| 118 | + MetricsHandler: metricsHandler, |
| 119 | + }) |
| 120 | + |
| 121 | + addr := fmt.Sprintf("%s:%d", cfg.App.Host, cfg.App.Port) |
| 122 | + srv := &http.Server{ |
| 123 | + Addr: addr, |
| 124 | + Handler: engine, |
| 125 | + ReadHeaderTimeout: 30 * time.Second, |
| 126 | + WriteTimeout: 15 * time.Minute, |
| 127 | + IdleTimeout: 120 * time.Second, |
| 128 | + } |
| 129 | + |
| 130 | + go func() { |
| 131 | + log.Sugar().Infow("starting admin http server", "addr", addr) |
| 132 | + log.Sugar().Infow("swagger url", "url", addr+"/swagger/index.html") |
| 133 | + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { |
| 134 | + log.Sugar().Fatalw("listen error", "err", err) |
| 135 | + } |
| 136 | + }() |
| 137 | + |
| 138 | + // graceful shutdown |
| 139 | + quit := make(chan os.Signal, 1) |
| 140 | + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) |
| 141 | + <-quit |
| 142 | + |
| 143 | + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| 144 | + defer cancel() |
| 145 | + if err := srv.Shutdown(ctx); err != nil { |
| 146 | + log.Sugar().Errorw("server shutdown", "err", err) |
| 147 | + } |
| 148 | + log.Sugar().Info("admin server exited") |
| 149 | +} |
0 commit comments