package server import ( "context" "fmt" "log" "net" "net/http" "strings" "time" "datarush/internal/auth/config" grpcHandlers "datarush/internal/auth/handler/grpc" httpHandlers "datarush/internal/auth/handler/http" authPostgresRepo "datarush/internal/auth/repository/postgres" "datarush/internal/auth/service" pb "datarush/pkg/api/auth" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) const ( httpReadTimeout = 10 * time.Second httpWriteTimeout = 10 * time.Second httpIdleTimeout = 60 * time.Second ) type Server struct { grpcServer *grpc.Server config *config.Config db *sqlx.DB httpServer *http.Server } func New(cfg *config.Config) *Server { return &Server{ config: cfg, } } func (s *Server) Start() error { // Connect to database db, err := sqlx.Connect("postgres", s.config.BuildPostgresConnStr()) if err != nil { return fmt.Errorf("failed to connect to postgres: %w", err) } s.db = db // Create tables if err := s.createTables(); err != nil { return fmt.Errorf("failed to create tables: %w", err) } // Register gRPC services if err := s.registerGRPCServices(); err != nil { return fmt.Errorf("failed to register gRPC services: %w", err) } // Start gRPC server in a goroutine go func() { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.config.GRPCPort)) if err != nil { log.Fatalf("failed to listen on grpc port: %v", err) } log.Printf("starting gRPC server on port %d", s.config.GRPCPort) if err := s.grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve gRPC: %v", err) } }() // Start HTTP server if err := s.startHTTPServer(); err != nil { return fmt.Errorf("failed to start HTTP server: %w", err) } return nil } func (s *Server) registerGRPCServices() error { s.grpcServer = grpc.NewServer() // Create repositories userRepo := authPostgresRepo.NewUserRepository(s.db) // Create services authService := service.NewAuthService(userRepo, s.config.JWTSecret) // Create and register handlers authHandler := grpcHandlers.NewAuthHandler(authService) pb.RegisterAuthServiceServer(s.grpcServer, authHandler) // Enable reflection if configured if s.config.GRPCEnableReflection { reflection.Register(s.grpcServer) } return nil } func (s *Server) startHTTPServer() error { // Create repositories userRepo := authPostgresRepo.NewUserRepository(s.db) // Create services authService := service.NewAuthService(userRepo, s.config.JWTSecret) // Create HTTP handlers authHandler := httpHandlers.NewAuthHandler(authService) userHandler := httpHandlers.NewUserHandler(authService) // Create router mux := http.NewServeMux() // Register routes with middleware wrapper mux.HandleFunc("/api/v1/sign-up", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } authHandler.SignUp(w, r) }) mux.HandleFunc("/api/v1/sign-in", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } authHandler.SignIn(w, r) }) mux.HandleFunc("/api/v1/me", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } userHandler.GetMe(w, r) }) mux.HandleFunc("/api/v1/users/", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Extract user_id from path /api/v1/users/:user_id userID := strings.TrimPrefix(r.URL.Path, "/api/v1/users/") r.Header.Set("X-User-ID", userID) userHandler.GetUser(w, r) }) mux.HandleFunc("/api/v1/me/stat", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } userHandler.GetMyStat(w, r) }) mux.HandleFunc("/api/v1/leaderboard", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } userHandler.GetLeaderboard(w, r) }) s.httpServer = &http.Server{ Addr: fmt.Sprintf(":%d", s.config.HTTPPort), Handler: mux, ReadTimeout: httpReadTimeout, WriteTimeout: httpWriteTimeout, IdleTimeout: httpIdleTimeout, } go func() { log.Printf("starting HTTP server on port %d", s.config.HTTPPort) if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("failed to start HTTP server: %v", err) } }() return nil } func (s *Server) Stop() { log.Println("shutting down auth server...") if s.httpServer != nil { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := s.httpServer.Shutdown(ctx); err != nil { log.Printf("failed to shutdown HTTP server: %v", err) } } if s.grpcServer != nil { s.grpcServer.GracefulStop() } if s.db != nil { if err := s.db.Close(); err != nil { log.Printf("failed to close database: %v", err) } } log.Println("auth server stopped") } func (s *Server) createTables() error { schema := ` CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); ` _, err := s.db.Exec(schema) return err }