package main import ( "context" "database/sql" "encoding/hex" "flag" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" _ "github.com/lib/pq" "github.com/rwadurian/mpc-system/pkg/config" "github.com/rwadurian/mpc-system/pkg/crypto" "github.com/rwadurian/mpc-system/pkg/logger" "github.com/rwadurian/mpc-system/services/server-party/adapters/output/postgres" "github.com/rwadurian/mpc-system/services/server-party/application/use_cases" "go.uber.org/zap" ) func main() { // Parse flags configPath := flag.String("config", "", "Path to config file") flag.Parse() // Load configuration cfg, err := config.Load(*configPath) if err != nil { fmt.Printf("Failed to load config: %v\n", err) os.Exit(1) } // Initialize logger if err := logger.Init(&logger.Config{ Level: cfg.Logger.Level, Encoding: cfg.Logger.Encoding, }); err != nil { fmt.Printf("Failed to initialize logger: %v\n", err) os.Exit(1) } defer logger.Sync() logger.Info("Starting Server Party Service", zap.String("environment", cfg.Server.Environment), zap.Int("http_port", cfg.Server.HTTPPort)) // Initialize database connection db, err := initDatabase(cfg.Database) if err != nil { logger.Fatal("Failed to connect to database", zap.Error(err)) } defer db.Close() // Initialize crypto service with master key from environment masterKeyHex := os.Getenv("MPC_CRYPTO_MASTER_KEY") if masterKeyHex == "" { masterKeyHex = "0123456789abcdef0123456789abcdef" // Default 32 bytes for development } masterKey, err := hex.DecodeString(masterKeyHex) if err != nil { logger.Fatal("Invalid master key format", zap.Error(err)) } cryptoService, err := crypto.NewCryptoService(masterKey) if err != nil { logger.Fatal("Failed to create crypto service", zap.Error(err)) } // Initialize repositories keyShareRepo := postgres.NewKeySharePostgresRepo(db) // Initialize use cases // Note: SessionCoordinatorClient and MessageRouterClient would be // implemented as gRPC clients in production participateKeygenUC := use_cases.NewParticipateKeygenUseCase( keyShareRepo, nil, // sessionClient - would be gRPC client nil, // messageRouter - would be gRPC client cryptoService, ) participateSigningUC := use_cases.NewParticipateSigningUseCase( keyShareRepo, nil, // sessionClient nil, // messageRouter cryptoService, ) // Create shutdown context ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Start HTTP server errChan := make(chan error, 1) go func() { if err := startHTTPServer(cfg, participateKeygenUC, participateSigningUC); err != nil { errChan <- fmt.Errorf("HTTP server error: %w", err) } }() // Wait for shutdown signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) select { case sig := <-sigChan: logger.Info("Received shutdown signal", zap.String("signal", sig.String())) case err := <-errChan: logger.Error("Server error", zap.Error(err)) } // Graceful shutdown logger.Info("Shutting down...") cancel() time.Sleep(5 * time.Second) logger.Info("Shutdown complete") _ = ctx } func initDatabase(cfg config.DatabaseConfig) (*sql.DB, error) { db, err := sql.Open("postgres", cfg.DSN()) if err != nil { return nil, err } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) db.SetConnMaxLifetime(cfg.ConnMaxLife) if err := db.Ping(); err != nil { return nil, err } logger.Info("Connected to PostgreSQL") return db, nil } func startHTTPServer( cfg *config.Config, participateKeygenUC *use_cases.ParticipateKeygenUseCase, participateSigningUC *use_cases.ParticipateSigningUseCase, ) error { if cfg.Server.Environment == "production" { gin.SetMode(gin.ReleaseMode) } router := gin.New() router.Use(gin.Recovery()) router.Use(gin.Logger()) // Health check router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "service": "server-party", }) }) // API routes api := router.Group("/api/v1") { // Keygen participation endpoint api.POST("/keygen/participate", func(c *gin.Context) { var req struct { SessionID string `json:"session_id" binding:"required"` PartyID string `json:"party_id" binding:"required"` JoinToken string `json:"join_token" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Note: In production, this would trigger async keygen participation c.JSON(http.StatusAccepted, gin.H{ "message": "keygen participation initiated", "session_id": req.SessionID, "party_id": req.PartyID, }) }) // Signing participation endpoint api.POST("/sign/participate", func(c *gin.Context) { var req struct { SessionID string `json:"session_id" binding:"required"` PartyID string `json:"party_id" binding:"required"` JoinToken string `json:"join_token" binding:"required"` MessageHash string `json:"message_hash" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Note: In production, this would trigger async signing participation c.JSON(http.StatusAccepted, gin.H{ "message": "signing participation initiated", "session_id": req.SessionID, "party_id": req.PartyID, }) }) // Get key shares for a party api.GET("/shares/:party_id", func(c *gin.Context) { partyID := c.Param("party_id") // Note: In production, would fetch from repository c.JSON(http.StatusOK, gin.H{ "party_id": partyID, "shares": []interface{}{}, }) }) } // Placeholder for use cases to avoid unused variable warnings _ = participateKeygenUC _ = participateSigningUC logger.Info("Starting HTTP server", zap.Int("port", cfg.Server.HTTPPort)) return router.Run(fmt.Sprintf(":%d", cfg.Server.HTTPPort)) }