mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
This commit refactors the API server startup logic in `cmd/api/main.go` to simplify the application's architecture. Key changes: - Consolidates the three separate HTTP servers (GraphQL API, GraphQL Playground, and Prometheus metrics) into a single `http.Server` instance. - Uses a single `http.ServeMux` to route requests to the appropriate handlers on distinct paths (`/query`, `/playground`, `/metrics`). - Removes the now-redundant `PlaygroundPort` from the application's configuration. This change simplifies the server startup and shutdown logic, reduces resource usage, and makes the application's entry point cleaner and easier to maintain.
194 lines
5.5 KiB
Go
194 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"syscall"
|
|
"tercul/internal/app"
|
|
"tercul/internal/app/analytics"
|
|
graph "tercul/internal/adapters/graphql"
|
|
dbsql "tercul/internal/data/sql"
|
|
"tercul/internal/jobs/linguistics"
|
|
"tercul/internal/observability"
|
|
"tercul/internal/platform/auth"
|
|
"tercul/internal/platform/config"
|
|
"tercul/internal/platform/db"
|
|
"tercul/internal/platform/log"
|
|
"tercul/internal/platform/search"
|
|
"time"
|
|
|
|
"github.com/99designs/gqlgen/graphql/playground"
|
|
"github.com/pressly/goose/v3"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/weaviate/weaviate-go-client/v5/weaviate"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// runMigrations applies database migrations using goose.
|
|
func runMigrations(gormDB *gorm.DB) error {
|
|
sqlDB, err := gormDB.DB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := goose.SetDialect("postgres"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// This is brittle. A better approach might be to use an env var or config.
|
|
_, b, _, _ := runtime.Caller(0)
|
|
migrationsDir := filepath.Join(filepath.Dir(b), "../../internal/data/migrations")
|
|
|
|
log.Info(fmt.Sprintf("Applying database migrations from %s", migrationsDir))
|
|
if err := goose.Up(sqlDB, migrationsDir); err != nil {
|
|
return err
|
|
}
|
|
log.Info("Database migrations applied successfully")
|
|
return nil
|
|
}
|
|
|
|
// main is the entry point for the Tercul application.
|
|
func main() {
|
|
// Load configuration from environment variables
|
|
config.LoadConfig()
|
|
|
|
// Initialize logger
|
|
log.Init("tercul-api", config.Cfg.Environment)
|
|
obsLogger := observability.NewLogger("tercul-api", config.Cfg.Environment)
|
|
|
|
// Initialize OpenTelemetry Tracer Provider
|
|
tp, err := observability.TracerProvider("tercul-api", config.Cfg.Environment)
|
|
if err != nil {
|
|
log.Fatal(err, "Failed to initialize OpenTelemetry tracer")
|
|
}
|
|
defer func() {
|
|
if err := tp.Shutdown(context.Background()); err != nil {
|
|
log.Error(err, "Error shutting down tracer provider")
|
|
}
|
|
}()
|
|
|
|
// Initialize Prometheus metrics
|
|
reg := prometheus.NewRegistry()
|
|
metrics := observability.NewMetrics(reg) // Metrics are registered automatically
|
|
|
|
log.Info(fmt.Sprintf("Starting Tercul application in %s environment, version 1.0.0", config.Cfg.Environment))
|
|
|
|
// Initialize database connection
|
|
database, err := db.InitDB(metrics)
|
|
if err != nil {
|
|
log.Fatal(err, "Failed to initialize database")
|
|
}
|
|
defer db.Close()
|
|
|
|
if err := runMigrations(database); err != nil {
|
|
log.Fatal(err, "Failed to apply database migrations")
|
|
}
|
|
|
|
// Initialize Weaviate client
|
|
weaviateCfg := weaviate.Config{
|
|
Host: config.Cfg.WeaviateHost,
|
|
Scheme: config.Cfg.WeaviateScheme,
|
|
}
|
|
weaviateClient, err := weaviate.NewClient(weaviateCfg)
|
|
if err != nil {
|
|
log.Fatal(err, "Failed to create weaviate client")
|
|
}
|
|
|
|
// Create search client
|
|
searchClient := search.NewWeaviateWrapper(weaviateClient)
|
|
|
|
// Create repositories
|
|
repos := dbsql.NewRepositories(database)
|
|
|
|
// Create linguistics dependencies
|
|
analysisRepo := linguistics.NewGORMAnalysisRepository(database)
|
|
sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
|
|
if err != nil {
|
|
log.Fatal(err, "Failed to create sentiment provider")
|
|
}
|
|
|
|
// Create platform components
|
|
jwtManager := auth.NewJWTManager()
|
|
|
|
// Create application services
|
|
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
|
|
|
// Create application dependencies
|
|
deps := app.Dependencies{
|
|
WorkRepo: repos.Work,
|
|
UserRepo: repos.User,
|
|
AuthorRepo: repos.Author,
|
|
TranslationRepo: repos.Translation,
|
|
CommentRepo: repos.Comment,
|
|
LikeRepo: repos.Like,
|
|
BookmarkRepo: repos.Bookmark,
|
|
CollectionRepo: repos.Collection,
|
|
TagRepo: repos.Tag,
|
|
CategoryRepo: repos.Category,
|
|
BookRepo: repos.Book,
|
|
PublisherRepo: repos.Publisher,
|
|
SourceRepo: repos.Source,
|
|
CopyrightRepo: repos.Copyright,
|
|
MonetizationRepo: repos.Monetization,
|
|
AnalyticsRepo: repos.Analytics,
|
|
AuthRepo: repos.Auth,
|
|
LocalizationRepo: repos.Localization,
|
|
SearchClient: searchClient,
|
|
AnalyticsService: analyticsService,
|
|
JWTManager: jwtManager,
|
|
}
|
|
|
|
// Create application
|
|
application := app.NewApplication(deps)
|
|
|
|
// Create GraphQL server
|
|
resolver := &graph.Resolver{
|
|
App: application,
|
|
}
|
|
|
|
// Create handlers
|
|
apiHandler := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
|
playgroundHandler := playground.Handler("GraphQL Playground", "/query")
|
|
metricsHandler := observability.PrometheusHandler(reg)
|
|
|
|
// Consolidate handlers into a single mux
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/query", apiHandler)
|
|
mux.Handle("/playground", playgroundHandler)
|
|
mux.Handle("/metrics", metricsHandler)
|
|
|
|
// Create a single HTTP server
|
|
mainServer := &http.Server{
|
|
Addr: config.Cfg.ServerPort,
|
|
Handler: mux,
|
|
}
|
|
log.Info(fmt.Sprintf("API server listening on port %s", config.Cfg.ServerPort))
|
|
|
|
// Start the main server in a goroutine
|
|
go func() {
|
|
if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatal(err, "Failed to start server")
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal to gracefully shutdown the server
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
<-quit
|
|
log.Info("Shutting down server...")
|
|
|
|
// Graceful shutdown
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if err := mainServer.Shutdown(ctx); err != nil {
|
|
log.Error(err, "Server forced to shutdown")
|
|
}
|
|
|
|
log.Info("Server shut down successfully")
|
|
} |