Files
Turbine/pkg/ring/main.go
2025-12-13 22:51:11 +08:00

171 lines
5.1 KiB
Go

package main
import (
"fmt"
"net"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"git.solsynth.dev/goatworks/turbine/pkg/ring/clients" // Add this import
"git.solsynth.dev/goatworks/turbine/pkg/ring/infra"
"git.solsynth.dev/goatworks/turbine/pkg/ring/routes"
"git.solsynth.dev/goatworks/turbine/pkg/ring/services"
"git.solsynth.dev/goatworks/turbine/pkg/ring/websocket" // Add this import
"git.solsynth.dev/goatworks/turbine/pkg/shared/hash"
pb "git.solsynth.dev/goatworks/turbine/pkg/shared/proto/gen"
"git.solsynth.dev/goatworks/turbine/pkg/shared/registrar"
fiber_websocket "github.com/gofiber/contrib/v3/websocket" // Add this alias import
"github.com/gofiber/fiber/v3"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
"google.golang.org/grpc"
)
func init() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}
func main() {
log.Info().Msg("Starting Turbine Ring...")
viper.SetConfigName("settings")
viper.AddConfigPath(".")
viper.AddConfigPath("/etc/turbine/")
viper.SetConfigType("toml")
log.Info().Msg("Reading configuration...")
if err := viper.ReadInConfig(); err != nil {
log.Fatal().Err(err).Msg("Failed to read config file...")
}
log.Info().Msg("Configuration loaded.")
clients.InitPushClients() // Initialize APNs and Firebase clients
clients.InitSMTPSettings() // Initialize SMTP client settings
clients.InitNATSClient() // Initialize NATS client
// --- gRPC Server ---
grpcListenAddr := viper.GetString("grpc.listen")
if grpcListenAddr == "" {
log.Fatal().Msg("grpc.listen not configured")
}
grpcServer := grpc.NewServer()
wsManager := websocket.NewManager(clients.GetNATSClient()) // Pass NATS client to WebSocket Manager
ringService := &services.RingServiceServerImpl{
WsManager: wsManager, // Inject WebSocket Manager into RingService
}
pb.RegisterRingServiceServer(grpcServer, ringService)
pb.RegisterRingHandlerServiceServer(grpcServer, ringService)
grpcLis, err := net.Listen("tcp", grpcListenAddr)
if err != nil {
log.Fatal().Err(err).Msgf("Failed to listen for gRPC: %s", grpcListenAddr)
}
go func() {
log.Info().Msgf("gRPC server listening on %s", grpcListenAddr)
if err := grpcServer.Serve(grpcLis); err != nil {
log.Fatal().Err(err).Msg("Failed to serve gRPC")
}
}()
// --- Service Registration ---
etcdEndpoints := viper.GetStringSlice("etcd.endpoints")
if len(etcdEndpoints) == 0 {
log.Fatal().Msg("etcd.endpoints not configured")
}
if err := infra.ConnectDb(); err != nil {
log.Fatal().Err(err).Msg("Failed to connect database.")
} else {
infra.Db.AutoMigrate()
}
if viper.GetBool("etcd.insecure") {
for i, ep := range etcdEndpoints {
if !strings.HasPrefix(ep, "http://") && !strings.HasPrefix(ep, "https://") {
etcdEndpoints[i] = "http://" + ep
}
}
}
serviceReg, err := registrar.NewServiceRegistrar(etcdEndpoints)
if err != nil {
log.Fatal().Err(err).Msg("Failed to create service registrar")
}
listenAddr := viper.GetString("listen")
host := viper.GetString("host")
if host == "" {
log.Fatal().Msg("host not configured")
}
portStr := strings.TrimPrefix(listenAddr, ":")
port, err := strconv.Atoi(portStr)
if err != nil {
log.Fatal().Err(err).Msg("Invalid listen address")
}
serviceName := "ring" // This should probably be "ring"
instanceID := fmt.Sprint(hash.Hash(fmt.Sprintf("%s-%s-%d", serviceName, host, port)))[:8]
err = serviceReg.Register(serviceName, "http", instanceID, host, port, 30)
if err != nil {
log.Fatal().Err(err).Msg("Failed to register service")
}
log.Info().Str("service", serviceName).Str("instanceID", instanceID).Msg("Service registered successfully")
// --- Web Server ---
app := fiber.New(fiber.Config{
ServerHeader: "Turbine Ring",
})
api := app.Group("/api")
{
api.Post("/notifications/subscription", routes.CreatePushSubscription)
}
// Initialize WebSocket Controller
wsController := websocket.NewWebSocketController(wsManager)
// WebSocket endpoint
app.Use("/ws", func(c fiber.Ctx) error {
// Mock authentication based on C# example
// In a real scenario, you'd extract user/session from JWT or similar
// and set c.Locals("currentUser") and c.Locals("currentSession")
c.Locals("currentUser", &pb.Account{Id: uuid.New().String(), Name: "mock_user", Nick: "Mock User"})
c.Locals("currentSession", &pb.AuthSession{ClientId: lo.ToPtr(uuid.New().String())})
if fiber_websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
return fiber.ErrUpgradeRequired
})
app.Get("/ws", fiber_websocket.New(wsController.HandleWebSocket))
// Graceful shutdown
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
log.Info().Msg("Shutting down servers...")
if err := app.ShutdownWithTimeout(5 * time.Second); err != nil {
log.Error().Err(err).Msg("Fiber server shutdown error")
}
grpcServer.GracefulStop()
log.Info().Msg("Servers gracefully stopped")
}()
err = app.Listen(listenAddr, fiber.ListenConfig{DisableStartupMessage: true})
if err != nil {
log.Fatal().Err(err).Msg("Failed to start the server...")
}
}