package main import ( "context" "encoding/json" "flag" "fmt" "log" "net" "net/http" "os" "os/signal" "syscall" "github.com/cloudprint/cloudprint-backend/config" "github.com/cloudprint/cloudprint-backend/internal/grpc/interceptor" grpcService "github.com/cloudprint/cloudprint-backend/internal/grpc/service" "github.com/cloudprint/cloudprint-backend/internal/repository" "github.com/cloudprint/cloudprint-backend/internal/service" "github.com/cloudprint/cloudprint-backend/pkg/jwt" "github.com/cloudprint/cloudprint-backend/pkg/pay" "github.com/cloudprint/cloudprint-backend/pkg/upload" pb "github.com/cloudprint/cloudprint-backend/proto/generated" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) func main() { configPath := flag.String("config", "config.yaml", "path to config file") flag.Parse() // Load configuration cfg, err := config.LoadConfig(*configPath) if err != nil { log.Fatalf("Failed to load config: %v", err) } // Initialize logger zapLogger, _ := zap.NewProduction() defer zapLogger.Sync() // Initialize database db, err := initDB(cfg) if err != nil { log.Fatalf("Failed to initialize database: %v", err) } // Initialize repositories userRepo := repository.NewUserRepository(db) adminRepo := repository.NewAdminRepository(db) fileRepo := repository.NewFileRepository(db) orderRepo := repository.NewOrderRepository(db) orderItemRepo := repository.NewOrderItemRepository(db) printerRepo := repository.NewPrinterRepository(db) printJobRepo := repository.NewPrintJobRepository(db) libraryRepo := repository.NewLibraryRepository(db) priceRepo := repository.NewPriceRepository(db) paymentRepo := repository.NewPaymentRepository(db) // Initialize JWT manager jwtManager := jwt.NewJWTManager( cfg.JWT.Secret, cfg.JWT.AccessTokenTTL, cfg.JWT.RefreshTokenTTL, ) // Initialize file uploader fileUploader := upload.NewFileUploader( cfg.Upload.Path, cfg.Upload.ChunkPath, cfg.Upload.MaxSize, ) // Initialize WeChat pay weChatPay := pay.NewWeChatPay(&cfg.WeChat) // Initialize services authService := service.NewAuthService(userRepo, adminRepo, jwtManager, nil, &cfg.WeChat) fileService := service.NewFileService(fileRepo, fileUploader, cfg.Upload.Path) orderService := service.NewOrderService(orderRepo, orderItemRepo, fileRepo, priceRepo, userRepo, printerRepo, printJobRepo) printService := service.NewPrintService(printerRepo, printJobRepo, orderRepo) // Initialize gRPC server grpcServer := grpc.NewServer( grpc.UnaryInterceptor(interceptor.NewAuthInterceptor(jwtManager).UnaryServerInterceptor()), ) // Register gRPC services pb.RegisterAuthServiceServer(grpcServer, grpcService.NewAuthServiceServer(authService)) pb.RegisterFileServiceServer(grpcServer, grpcService.NewFileServiceServer(fileService)) pb.RegisterOrderServiceServer(grpcServer, grpcService.NewOrderServiceServer(orderService)) pb.RegisterPrintServiceServer(grpcServer, grpcService.NewPrintServiceServer(printService)) pb.RegisterLibraryServiceServer(grpcServer, grpcService.NewLibraryServiceServer(libraryRepo, fileRepo)) pb.RegisterPriceServiceServer(grpcServer, grpcService.NewPriceServiceServer(priceRepo)) pb.RegisterPayServiceServer(grpcServer, grpcService.NewPayServiceServer(orderService, paymentRepo, weChatPay, userRepo)) pb.RegisterAdminServiceServer(grpcServer, grpcService.NewAdminServiceServer(authService, orderRepo, printerRepo)) // Initialize HTTP gateway gateway := newHTTPGateway(grpcServer, jwtManager) // Start server addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) lis, err := net.Listen("tcp", addr) if err != nil { log.Fatalf("Failed to listen: %v", err) } // Graceful shutdown sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) // HTTP server for gateway httpServer := &http.Server{ Handler: gateway, } // Start HTTP server in goroutine go func() { zapLogger.Info("Starting HTTP gateway", zap.String("address", addr)) if err := httpServer.Serve(lis); err != nil && err != http.ErrServerClosed { log.Fatalf("Failed to serve HTTP: %v", err) } }() // Wait for shutdown signal <-sigCh zapLogger.Info("Shutting down servers...") // Graceful stop gRPC server grpcServer.GracefulStop() // Shutdown HTTP server if err := httpServer.Close(); err != nil { zapLogger.Error("Failed to close HTTP server", zap.Error(err)) } } func initDB(cfg *config.Config) (*gorm.DB, error) { gormLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ LogLevel: logger.Info, }, ) db, err := gorm.Open(postgres.Open(cfg.Database.DSN()), &gorm.Config{ Logger: gormLogger, }) if err != nil { return nil, err } sqlDB, err := db.DB() if err != nil { return nil, err } sqlDB.SetMaxOpenConns(cfg.Database.MaxOpenConns) sqlDB.SetMaxIdleConns(cfg.Database.MaxIdleConns) return db, nil } // HTTP Gateway type httpGateway struct { grpcServer *grpc.Server jwtManager *jwt.JWTManager mux *http.ServeMux } func newHTTPGateway(grpcServer *grpc.Server, jwtManager *jwt.JWTManager) *httpGateway { g := &httpGateway{ grpcServer: grpcServer, jwtManager: jwtManager, mux: http.NewServeMux(), } g.registerHandlers() return g } func (g *httpGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { g.mux.ServeHTTP(w, r) } func (g *httpGateway) registerHandlers() { // Health check g.mux.HandleFunc("/health", g.healthHandler) // Auth endpoints g.mux.HandleFunc("/api/v1/auth/login", g.handleLogin) g.mux.HandleFunc("/api/v1/auth/refresh", g.handleRefreshToken) g.mux.HandleFunc("/api/v1/auth/userinfo", g.handleGetUserInfo) // File endpoints g.mux.HandleFunc("/api/v1/file/upload", g.handleFileUpload) g.mux.HandleFunc("/api/v1/file/", g.handleFileOps) // Order endpoints g.mux.HandleFunc("/api/v1/order/create", g.handleCreateOrder) g.mux.HandleFunc("/api/v1/order/", g.handleOrderOps) // Print endpoints g.mux.HandleFunc("/api/v1/print/printers", g.handleGetPrinters) g.mux.HandleFunc("/api/v1/print/submit", g.handleSubmitPrintJob) // Library endpoints g.mux.HandleFunc("/api/v1/library/categories", g.handleGetCategories) g.mux.HandleFunc("/api/v1/library/files", g.handleGetLibraryFiles) // Price endpoints g.mux.HandleFunc("/api/v1/price/list", g.handleGetPriceList) // Pay endpoints g.mux.HandleFunc("/api/v1/pay/wxpay", g.handleCreateWxPayOrder) g.mux.HandleFunc("/api/v1/pay/callback", g.handlePayCallback) g.mux.HandleFunc("/api/v1/pay/balance", g.handlePayByBalance) // Admin endpoints g.mux.HandleFunc("/api/v1/admin/login", g.handleAdminLogin) g.mux.HandleFunc("/api/v1/admin/stats", g.handleGetStatistics) } func (g *httpGateway) healthHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok"}`)) } func (g *httpGateway) writeJSON(w http.ResponseWriter, statusCode int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) json.NewEncoder(w).Encode(data) } func (g *httpGateway) handleError(w http.ResponseWriter, err error) { st, ok := status.FromError(err) if !ok { g.writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } code := http.StatusInternalServerError switch st.Code() { case codes.OK: code = http.StatusOK case codes.InvalidArgument: code = http.StatusBadRequest case codes.NotFound: code = http.StatusNotFound case codes.Unauthenticated: code = http.StatusUnauthorized case codes.PermissionDenied: code = http.StatusForbidden } g.writeJSON(w, code, map[string]string{"error": st.Message()}) } func (g *httpGateway) parseToken(r *http.Request) (context.Context, error) { token := r.Header.Get("Authorization") if token == "" { return nil, status.Error(codes.Unauthenticated, "missing token") } if len(token) > 7 && token[:7] == "Bearer " { token = token[7:] } claims, err := g.jwtManager.ValidateToken(token) if err != nil { return nil, status.Error(codes.Unauthenticated, "invalid token") } return metadata.NewOutgoingContext(r.Context(), metadata.Pairs( "user_id", fmt.Sprintf("%d", claims.UserID), "user_type", claims.UserType, )), nil } func (g *httpGateway) handleLogin(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req pb.LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // gRPC call would go here - returning placeholder for now g.writeJSON(w, http.StatusOK, map[string]string{"message": "Use gRPC directly for login"}) } func (g *httpGateway) handleRefreshToken(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleGetUserInfo(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleFileUpload(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleFileOps(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } switch r.Method { case http.MethodGet: g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) case http.MethodDelete: g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } func (g *httpGateway) handleCreateOrder(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleOrderOps(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } switch r.Method { case http.MethodGet: g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } func (g *httpGateway) handleGetPrinters(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleSubmitPrintJob(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleGetCategories(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleGetLibraryFiles(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleGetPriceList(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleCreateWxPayOrder(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handlePayCallback(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handlePayByBalance(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleAdminLogin(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) } func (g *httpGateway) handleGetStatistics(w http.ResponseWriter, r *http.Request) { _, err := g.parseToken(r) if err != nil { g.handleError(w, err) return } g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"}) }