UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,964 lines (1,650 loc) 53.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.grpcGoTemplate = void 0; exports.grpcGoTemplate = { id: 'grpc-go', name: 'grpc-go', displayName: 'gRPC Server (Go)', description: 'High-performance gRPC server with Protocol Buffers, streaming support, and interceptors', language: 'go', framework: 'grpc', version: '1.60.1', tags: ['go', 'grpc', 'protobuf', 'microservices', 'rpc', 'streaming', 'high-performance'], port: 50051, dependencies: {}, features: ['authentication', 'validation', 'logging', 'monitoring', 'microservices', 'documentation'], files: { // Go module configuration 'go.mod': `module {{projectName}} go 1.21 require ( google.golang.org/grpc v1.60.1 google.golang.org/protobuf v1.32.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/envoyproxy/protoc-gen-validate v1.0.2 github.com/bufbuild/buf v1.28.1 github.com/golang-jwt/jwt/v5 v5.2.0 github.com/joho/godotenv v1.5.1 github.com/go-playground/validator/v10 v10.16.0 github.com/rs/zerolog v1.31.0 github.com/prometheus/client_golang v1.18.0 github.com/stretchr/testify v1.8.4 gorm.io/gorm v1.25.5 gorm.io/driver/postgres v1.5.4 gorm.io/driver/mysql v1.5.2 gorm.io/driver/sqlite v1.5.4 golang.org/x/crypto v0.17.0 github.com/google/uuid v1.5.0 github.com/redis/go-redis/v9 v9.3.1 go.opentelemetry.io/otel v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 go.opentelemetry.io/otel/exporters/jaeger v1.17.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/jackc/pgx/v5 v5.5.1 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) `, // Main application entry point 'main.go': `package main import ( "context" "fmt" "log" "net" "os" "os/signal" "syscall" "time" "{{projectName}}/config" "{{projectName}}/database" "{{projectName}}/internal/interceptors" "{{projectName}}/internal/services" pb "{{projectName}}/proto/gen" "github.com/joho/godotenv" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/health" "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/reflection" ) // @title {{projectName}} gRPC Service // @version 1.0 // @description High-performance gRPC service with Protocol Buffers // @contact.name API Support // @contact.email support@example.com // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html func main() { // Load environment variables if err := godotenv.Load(); err != nil { log.Printf("Warning: .env file not found") } // Initialize configuration cfg := config.Load() // Initialize logger logger := initLogger(cfg.LogLevel) // Initialize database db, err := database.Initialize(cfg.DatabaseURL) if err != nil { logger.Fatal().Err(err).Msg("Failed to initialize database") } // Run migrations if err := database.Migrate(db); err != nil { logger.Fatal().Err(err).Msg("Failed to run migrations") } // Create gRPC server with interceptors serverOpts := []grpc.ServerOption{ grpc.ChainUnaryInterceptor( interceptors.LoggingUnaryInterceptor(logger), interceptors.AuthUnaryInterceptor(cfg.JWTSecret), interceptors.ValidationUnaryInterceptor(), interceptors.RecoveryUnaryInterceptor(logger), interceptors.MetricsUnaryInterceptor(), ), grpc.ChainStreamInterceptor( interceptors.LoggingStreamInterceptor(logger), interceptors.AuthStreamInterceptor(cfg.JWTSecret), interceptors.RecoveryStreamInterceptor(logger), interceptors.MetricsStreamInterceptor(), ), } grpcServer := grpc.NewServer(serverOpts...) // Register services userService := services.NewUserService(db, logger) pb.RegisterUserServiceServer(grpcServer, userService) productService := services.NewProductService(db, logger) pb.RegisterProductServiceServer(grpcServer, productService) // Register health check healthServer := health.NewServer() grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING) // Register reflection for grpcurl reflection.Register(grpcServer) // Create listener listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Port)) if err != nil { logger.Fatal().Err(err).Msg("Failed to create listener") } // Start server in goroutine go func() { logger.Info().Int("port", cfg.Port).Msg("Starting gRPC server") if err := grpcServer.Serve(listener); err != nil { logger.Fatal().Err(err).Msg("Failed to serve") } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) <-quit logger.Info().Msg("Shutting down server...") // Graceful shutdown ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() grpcServer.GracefulStop() logger.Info().Msg("Server exited") } func initLogger(level string) *zerolog.Logger { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix logLevel, err := zerolog.ParseLevel(level) if err != nil { logLevel = zerolog.InfoLevel } logger := zerolog.New(os.Stdout). Level(logLevel). With(). Timestamp(). Caller(). Logger() return &logger } `, // Configuration 'config/config.go': `package config import ( "os" "strconv" ) type Config struct { Port int DatabaseURL string JWTSecret string LogLevel string RedisURL string } func Load() *Config { return &Config{ Port: getEnvAsInt("PORT", 50051), DatabaseURL: getEnv("DATABASE_URL", "postgres://user:password@localhost/dbname?sslmode=disable"), JWTSecret: getEnv("JWT_SECRET", "your-secret-key"), LogLevel: getEnv("LOG_LEVEL", "info"), RedisURL: getEnv("REDIS_URL", "redis://localhost:6379/0"), } } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getEnvAsInt(key string, defaultValue int) int { valueStr := getEnv(key, "") if value, err := strconv.Atoi(valueStr); err == nil { return value } return defaultValue } `, // Protocol Buffers definitions 'proto/user.proto': `syntax = "proto3"; package {{projectName}}.v1; option go_package = "{{projectName}}/proto/gen;pb"; import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/empty.proto"; import "validate/validate.proto"; // User service for managing users service UserService { // Create a new user rpc CreateUser(CreateUserRequest) returns (User) { option (google.api.http) = { post: "/api/v1/users" body: "*" }; } // Get a user by ID rpc GetUser(GetUserRequest) returns (User) { option (google.api.http) = { get: "/api/v1/users/{id}" }; } // Update a user rpc UpdateUser(UpdateUserRequest) returns (User) { option (google.api.http) = { put: "/api/v1/users/{id}" body: "*" }; } // Delete a user rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/api/v1/users/{id}" }; } // List users with pagination rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) { option (google.api.http) = { get: "/api/v1/users" }; } // Login user rpc Login(LoginRequest) returns (LoginResponse) { option (google.api.http) = { post: "/api/v1/auth/login" body: "*" }; } // Stream user updates rpc StreamUserUpdates(StreamUserUpdatesRequest) returns (stream User) {} } // User message message User { string id = 1; string email = 2; string name = 3; UserRole role = 4; bool active = 5; google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp updated_at = 7; } // User role enum enum UserRole { USER_ROLE_UNSPECIFIED = 0; USER_ROLE_USER = 1; USER_ROLE_ADMIN = 2; USER_ROLE_MODERATOR = 3; } // Create user request message CreateUserRequest { string email = 1 [(validate.rules).string = {email: true, min_len: 1}]; string password = 2 [(validate.rules).string = {min_len: 8}]; string name = 3 [(validate.rules).string = {min_len: 1}]; UserRole role = 4; } // Get user request message GetUserRequest { string id = 1 [(validate.rules).string = {uuid: true}]; } // Update user request message UpdateUserRequest { string id = 1 [(validate.rules).string = {uuid: true}]; string email = 2 [(validate.rules).string = {email: true}]; string name = 3; UserRole role = 4; bool active = 5; } // Delete user request message DeleteUserRequest { string id = 1 [(validate.rules).string = {uuid: true}]; } // List users request message ListUsersRequest { int32 page = 1 [(validate.rules).int32 = {gte: 1}]; int32 limit = 2 [(validate.rules).int32 = {gte: 1, lte: 100}]; string search = 3; UserRole role = 4; } // List users response message ListUsersResponse { repeated User users = 1; int32 total = 2; int32 page = 3; int32 limit = 4; } // Login request message LoginRequest { string email = 1 [(validate.rules).string = {email: true, min_len: 1}]; string password = 2 [(validate.rules).string = {min_len: 1}]; } // Login response message LoginResponse { string access_token = 1; string refresh_token = 2; User user = 3; } // Stream user updates request message StreamUserUpdatesRequest { repeated string user_ids = 1; } `, 'proto/product.proto': `syntax = "proto3"; package {{projectName}}.v1; option go_package = "{{projectName}}/proto/gen;pb"; import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/empty.proto"; import "validate/validate.proto"; // Product service for managing products service ProductService { // Create a new product rpc CreateProduct(CreateProductRequest) returns (Product) { option (google.api.http) = { post: "/api/v1/products" body: "*" }; } // Get a product by ID rpc GetProduct(GetProductRequest) returns (Product) { option (google.api.http) = { get: "/api/v1/products/{id}" }; } // Update a product rpc UpdateProduct(UpdateProductRequest) returns (Product) { option (google.api.http) = { put: "/api/v1/products/{id}" body: "*" }; } // Delete a product rpc DeleteProduct(DeleteProductRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/api/v1/products/{id}" }; } // List products with pagination rpc ListProducts(ListProductsRequest) returns (ListProductsResponse) { option (google.api.http) = { get: "/api/v1/products" }; } // Batch create products rpc BatchCreateProducts(stream CreateProductRequest) returns (stream Product) {} } // Product message message Product { string id = 1; string name = 2; string description = 3; double price = 4; int32 stock = 5; string category = 6; bool active = 7; google.protobuf.Timestamp created_at = 8; google.protobuf.Timestamp updated_at = 9; } // Create product request message CreateProductRequest { string name = 1 [(validate.rules).string = {min_len: 1, max_len: 100}]; string description = 2 [(validate.rules).string = {max_len: 1000}]; double price = 3 [(validate.rules).double = {gt: 0}]; int32 stock = 4 [(validate.rules).int32 = {gte: 0}]; string category = 5 [(validate.rules).string = {min_len: 1}]; } // Get product request message GetProductRequest { string id = 1 [(validate.rules).string = {uuid: true}]; } // Update product request message UpdateProductRequest { string id = 1 [(validate.rules).string = {uuid: true}]; string name = 2 [(validate.rules).string = {min_len: 1, max_len: 100}]; string description = 3 [(validate.rules).string = {max_len: 1000}]; double price = 4 [(validate.rules).double = {gt: 0}]; int32 stock = 5 [(validate.rules).int32 = {gte: 0}]; string category = 6; bool active = 7; } // Delete product request message DeleteProductRequest { string id = 1 [(validate.rules).string = {uuid: true}]; } // List products request message ListProductsRequest { int32 page = 1 [(validate.rules).int32 = {gte: 1}]; int32 limit = 2 [(validate.rules).int32 = {gte: 1, lte: 100}]; string search = 3; string category = 4; double min_price = 5; double max_price = 6; } // List products response message ListProductsResponse { repeated Product products = 1; int32 total = 2; int32 page = 3; int32 limit = 4; } `, // Buf configuration 'buf.yaml': `version: v1 breaking: use: - FILE lint: use: - DEFAULT except: - FIELD_LOWER_SNAKE_CASE rpc_allow_same_request_response: false rpc_allow_google_protobuf_empty_requests: true rpc_allow_google_protobuf_empty_responses: true `, 'buf.gen.yaml': `version: v1 managed: enabled: true go_package_prefix: default: {{projectName}}/proto/gen plugins: - plugin: go out: proto/gen opt: paths=source_relative - plugin: go-grpc out: proto/gen opt: - paths=source_relative - require_unimplemented_servers=false - plugin: grpc-gateway out: proto/gen opt: - paths=source_relative - generate_unbound_methods=true - plugin: openapiv2 out: docs/openapi opt: - allow_merge=true - merge_file_name={{projectName}} `, // Models 'models/user.go': `package models import ( "time" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) type User struct { ID string \`gorm:"type:uuid;primary_key"\` Email string \`gorm:"uniqueIndex;not null"\` Password string \`gorm:"not null"\` Name string \`gorm:"not null"\` Role string \`gorm:"not null;default:'user'"\` Active bool \`gorm:"default:true"\` CreatedAt time.Time \`gorm:"not null"\` UpdatedAt time.Time \`gorm:"not null"\` DeletedAt gorm.DeletedAt \`gorm:"index"\` } func (u *User) BeforeCreate(tx *gorm.DB) error { u.ID = uuid.New().String() u.CreatedAt = time.Now() u.UpdatedAt = time.Now() return nil } func (u *User) BeforeUpdate(tx *gorm.DB) error { u.UpdatedAt = time.Now() return nil } func (u *User) SetPassword(password string) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } u.Password = string(hashedPassword) return nil } func (u *User) CheckPassword(password string) bool { err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) return err == nil } `, 'models/product.go': `package models import ( "time" "github.com/google/uuid" "gorm.io/gorm" ) type Product struct { ID string \`gorm:"type:uuid;primary_key"\` Name string \`gorm:"not null"\` Description string \`gorm:"type:text"\` Price float64 \`gorm:"not null;check:price >= 0"\` Stock int \`gorm:"not null;check:stock >= 0"\` Category string \`gorm:"not null"\` Active bool \`gorm:"default:true"\` CreatedAt time.Time \`gorm:"not null"\` UpdatedAt time.Time \`gorm:"not null"\` DeletedAt gorm.DeletedAt \`gorm:"index"\` } func (p *Product) BeforeCreate(tx *gorm.DB) error { p.ID = uuid.New().String() p.CreatedAt = time.Now() p.UpdatedAt = time.Now() return nil } func (p *Product) BeforeUpdate(tx *gorm.DB) error { p.UpdatedAt = time.Now() return nil } `, // Services 'internal/services/user_service.go': `package services import ( "context" "errors" "{{projectName}}/models" pb "{{projectName}}/proto/gen" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "github.com/rs/zerolog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" "gorm.io/gorm" ) type UserService struct { pb.UnimplementedUserServiceServer db *gorm.DB logger *zerolog.Logger } func NewUserService(db *gorm.DB, logger *zerolog.Logger) *UserService { return &UserService{ db: db, logger: logger, } } func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) { user := &models.User{ Email: req.Email, Name: req.Name, Role: req.Role.String(), } if err := user.SetPassword(req.Password); err != nil { return nil, status.Errorf(codes.Internal, "failed to hash password: %v", err) } if err := s.db.Create(user).Error; err != nil { s.logger.Error().Err(err).Msg("Failed to create user") if errors.Is(err, gorm.ErrDuplicatedKey) { return nil, status.Error(codes.AlreadyExists, "user with this email already exists") } return nil, status.Errorf(codes.Internal, "failed to create user: %v", err) } return s.userToProto(user), nil } func (s *UserService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) { var user models.User if err := s.db.First(&user, "id = ?", req.Id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.Error(codes.NotFound, "user not found") } return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } return s.userToProto(&user), nil } func (s *UserService) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.User, error) { var user models.User if err := s.db.First(&user, "id = ?", req.Id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.Error(codes.NotFound, "user not found") } return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } updates := map[string]interface{}{ "email": req.Email, "name": req.Name, "role": req.Role.String(), "active": req.Active, } if err := s.db.Model(&user).Updates(updates).Error; err != nil { return nil, status.Errorf(codes.Internal, "failed to update user: %v", err) } return s.userToProto(&user), nil } func (s *UserService) DeleteUser(ctx context.Context, req *pb.DeleteUserRequest) (*emptypb.Empty, error) { result := s.db.Delete(&models.User{}, "id = ?", req.Id) if result.Error != nil { return nil, status.Errorf(codes.Internal, "failed to delete user: %v", result.Error) } if result.RowsAffected == 0 { return nil, status.Error(codes.NotFound, "user not found") } return &emptypb.Empty{}, nil } func (s *UserService) ListUsers(ctx context.Context, req *pb.ListUsersRequest) (*pb.ListUsersResponse, error) { var users []models.User var total int64 query := s.db.Model(&models.User{}) if req.Search != "" { query = query.Where("name ILIKE ? OR email ILIKE ?", "%"+req.Search+"%", "%"+req.Search+"%") } if req.Role != pb.UserRole_USER_ROLE_UNSPECIFIED { query = query.Where("role = ?", req.Role.String()) } query.Count(&total) offset := (req.Page - 1) * req.Limit if err := query.Offset(int(offset)).Limit(int(req.Limit)).Find(&users).Error; err != nil { return nil, status.Errorf(codes.Internal, "failed to list users: %v", err) } userProtos := make([]*pb.User, len(users)) for i, user := range users { userProtos[i] = s.userToProto(&user) } return &pb.ListUsersResponse{ Users: userProtos, Total: int32(total), Page: req.Page, Limit: req.Limit, }, nil } func (s *UserService) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) { var user models.User if err := s.db.First(&user, "email = ?", req.Email).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.Error(codes.Unauthenticated, "invalid credentials") } return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } if !user.CheckPassword(req.Password) { return nil, status.Error(codes.Unauthenticated, "invalid credentials") } if !user.Active { return nil, status.Error(codes.PermissionDenied, "account is inactive") } // Generate tokens accessToken, err := s.generateToken(user.ID, "access", 15*60) // 15 minutes if err != nil { return nil, status.Errorf(codes.Internal, "failed to generate access token: %v", err) } refreshToken, err := s.generateToken(user.ID, "refresh", 7*24*60*60) // 7 days if err != nil { return nil, status.Errorf(codes.Internal, "failed to generate refresh token: %v", err) } return &pb.LoginResponse{ AccessToken: accessToken, RefreshToken: refreshToken, User: s.userToProto(&user), }, nil } func (s *UserService) StreamUserUpdates(req *pb.StreamUserUpdatesRequest, stream pb.UserService_StreamUserUpdatesServer) error { // This is a simplified example - in production you'd use a message queue or change data capture s.logger.Info().Strs("user_ids", req.UserIds).Msg("Starting user update stream") // Simulate streaming updates // In a real implementation, you'd subscribe to database changes or a message queue for { select { case <-stream.Context().Done(): return nil default: // Check for updates periodically // This is just for demonstration - use proper change detection in production } } } func (s *UserService) userToProto(user *models.User) *pb.User { role := pb.UserRole_USER_ROLE_USER switch user.Role { case "ADMIN": role = pb.UserRole_USER_ROLE_ADMIN case "MODERATOR": role = pb.UserRole_USER_ROLE_MODERATOR } return &pb.User{ Id: user.ID, Email: user.Email, Name: user.Name, Role: role, Active: user.Active, CreatedAt: timestamppb.New(user.CreatedAt), UpdatedAt: timestamppb.New(user.UpdatedAt), } } func (s *UserService) generateToken(userID string, tokenType string, expirySeconds int) (string, error) { claims := jwt.MapClaims{ "user_id": userID, "type": tokenType, "exp": time.Now().Add(time.Duration(expirySeconds) * time.Second).Unix(), "iat": time.Now().Unix(), "jti": uuid.New().String(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(os.Getenv("JWT_SECRET"))) } `, 'internal/services/product_service.go': `package services import ( "context" "errors" "io" "{{projectName}}/models" pb "{{projectName}}/proto/gen" "github.com/rs/zerolog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" "gorm.io/gorm" ) type ProductService struct { pb.UnimplementedProductServiceServer db *gorm.DB logger *zerolog.Logger } func NewProductService(db *gorm.DB, logger *zerolog.Logger) *ProductService { return &ProductService{ db: db, logger: logger, } } func (s *ProductService) CreateProduct(ctx context.Context, req *pb.CreateProductRequest) (*pb.Product, error) { product := &models.Product{ Name: req.Name, Description: req.Description, Price: req.Price, Stock: int(req.Stock), Category: req.Category, } if err := s.db.Create(product).Error; err != nil { s.logger.Error().Err(err).Msg("Failed to create product") return nil, status.Errorf(codes.Internal, "failed to create product: %v", err) } return s.productToProto(product), nil } func (s *ProductService) GetProduct(ctx context.Context, req *pb.GetProductRequest) (*pb.Product, error) { var product models.Product if err := s.db.First(&product, "id = ?", req.Id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.Error(codes.NotFound, "product not found") } return nil, status.Errorf(codes.Internal, "failed to get product: %v", err) } return s.productToProto(&product), nil } func (s *ProductService) UpdateProduct(ctx context.Context, req *pb.UpdateProductRequest) (*pb.Product, error) { var product models.Product if err := s.db.First(&product, "id = ?", req.Id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.Error(codes.NotFound, "product not found") } return nil, status.Errorf(codes.Internal, "failed to get product: %v", err) } updates := map[string]interface{}{ "name": req.Name, "description": req.Description, "price": req.Price, "stock": req.Stock, "category": req.Category, "active": req.Active, } if err := s.db.Model(&product).Updates(updates).Error; err != nil { return nil, status.Errorf(codes.Internal, "failed to update product: %v", err) } return s.productToProto(&product), nil } func (s *ProductService) DeleteProduct(ctx context.Context, req *pb.DeleteProductRequest) (*emptypb.Empty, error) { result := s.db.Delete(&models.Product{}, "id = ?", req.Id) if result.Error != nil { return nil, status.Errorf(codes.Internal, "failed to delete product: %v", result.Error) } if result.RowsAffected == 0 { return nil, status.Error(codes.NotFound, "product not found") } return &emptypb.Empty{}, nil } func (s *ProductService) ListProducts(ctx context.Context, req *pb.ListProductsRequest) (*pb.ListProductsResponse, error) { var products []models.Product var total int64 query := s.db.Model(&models.Product{}) if req.Search != "" { query = query.Where("name ILIKE ? OR description ILIKE ?", "%"+req.Search+"%", "%"+req.Search+"%") } if req.Category != "" { query = query.Where("category = ?", req.Category) } if req.MinPrice > 0 { query = query.Where("price >= ?", req.MinPrice) } if req.MaxPrice > 0 { query = query.Where("price <= ?", req.MaxPrice) } query.Count(&total) offset := (req.Page - 1) * req.Limit if err := query.Offset(int(offset)).Limit(int(req.Limit)).Find(&products).Error; err != nil { return nil, status.Errorf(codes.Internal, "failed to list products: %v", err) } productProtos := make([]*pb.Product, len(products)) for i, product := range products { productProtos[i] = s.productToProto(&product) } return &pb.ListProductsResponse{ Products: productProtos, Total: int32(total), Page: req.Page, Limit: req.Limit, }, nil } func (s *ProductService) BatchCreateProducts(stream pb.ProductService_BatchCreateProductsServer) error { for { req, err := stream.Recv() if err == io.EOF { return nil } if err != nil { return status.Errorf(codes.Internal, "failed to receive request: %v", err) } product := &models.Product{ Name: req.Name, Description: req.Description, Price: req.Price, Stock: int(req.Stock), Category: req.Category, } if err := s.db.Create(product).Error; err != nil { s.logger.Error().Err(err).Msg("Failed to create product in batch") continue // Skip failed products in batch } if err := stream.Send(s.productToProto(product)); err != nil { return status.Errorf(codes.Internal, "failed to send response: %v", err) } } } func (s *ProductService) productToProto(product *models.Product) *pb.Product { return &pb.Product{ Id: product.ID, Name: product.Name, Description: product.Description, Price: product.Price, Stock: int32(product.Stock), Category: product.Category, Active: product.Active, CreatedAt: timestamppb.New(product.CreatedAt), UpdatedAt: timestamppb.New(product.UpdatedAt), } } `, // Interceptors 'internal/interceptors/auth.go': `package interceptors import ( "context" "strings" "github.com/golang-jwt/jwt/v5" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) // List of methods that don't require authentication var publicMethods = map[string]bool{ "/{{projectName}}.v1.UserService/Login": true, "/{{projectName}}.v1.UserService/CreateUser": true, "/grpc.health.v1.Health/Check": true, } func AuthUnaryInterceptor(jwtSecret string) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { // Skip auth for public methods if publicMethods[info.FullMethod] { return handler(ctx, req) } // Extract token from metadata md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, status.Error(codes.Unauthenticated, "missing metadata") } authorization := md.Get("authorization") if len(authorization) == 0 { return nil, status.Error(codes.Unauthenticated, "missing authorization header") } // Parse Bearer token tokenString := strings.TrimPrefix(authorization[0], "Bearer ") if tokenString == authorization[0] { return nil, status.Error(codes.Unauthenticated, "invalid authorization format") } // Verify token token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, status.Error(codes.Unauthenticated, "invalid signing method") } return []byte(jwtSecret), nil }) if err != nil || !token.Valid { return nil, status.Error(codes.Unauthenticated, "invalid token") } // Extract claims claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, status.Error(codes.Unauthenticated, "invalid token claims") } // Add user ID to context userID, ok := claims["user_id"].(string) if !ok { return nil, status.Error(codes.Unauthenticated, "missing user_id in token") } ctx = context.WithValue(ctx, "user_id", userID) return handler(ctx, req) } } func AuthStreamInterceptor(jwtSecret string) grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { // Skip auth for public methods if publicMethods[info.FullMethod] { return handler(srv, ss) } // Extract token from metadata md, ok := metadata.FromIncomingContext(ss.Context()) if !ok { return status.Error(codes.Unauthenticated, "missing metadata") } authorization := md.Get("authorization") if len(authorization) == 0 { return status.Error(codes.Unauthenticated, "missing authorization header") } // Parse Bearer token tokenString := strings.TrimPrefix(authorization[0], "Bearer ") if tokenString == authorization[0] { return status.Error(codes.Unauthenticated, "invalid authorization format") } // Verify token token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, status.Error(codes.Unauthenticated, "invalid signing method") } return []byte(jwtSecret), nil }) if err != nil || !token.Valid { return status.Error(codes.Unauthenticated, "invalid token") } // Extract claims claims, ok := token.Claims.(jwt.MapClaims) if !ok { return status.Error(codes.Unauthenticated, "invalid token claims") } // Add user ID to context userID, ok := claims["user_id"].(string) if !ok { return status.Error(codes.Unauthenticated, "missing user_id in token") } // Create a new context with user ID ctx := context.WithValue(ss.Context(), "user_id", userID) // Wrap the stream with the new context wrappedStream := &wrappedServerStream{ ServerStream: ss, ctx: ctx, } return handler(srv, wrappedStream) } } type wrappedServerStream struct { grpc.ServerStream ctx context.Context } func (w *wrappedServerStream) Context() context.Context { return w.ctx } `, 'internal/interceptors/logging.go': `package interceptors import ( "context" "time" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/status" ) func LoggingUnaryInterceptor(logger *zerolog.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { start := time.Now() // Call the handler resp, err := handler(ctx, req) // Log the request duration := time.Since(start) statusCode := codes.OK if err != nil { if s, ok := status.FromError(err); ok { statusCode = s.Code() } } logger.Info(). Str("method", info.FullMethod). Dur("duration", duration). Str("status", statusCode.String()). Err(err). Msg("gRPC request") return resp, err } } func LoggingStreamInterceptor(logger *zerolog.Logger) grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { start := time.Now() // Call the handler err := handler(srv, ss) // Log the request duration := time.Since(start) statusCode := codes.OK if err != nil { if s, ok := status.FromError(err); ok { statusCode = s.Code() } } logger.Info(). Str("method", info.FullMethod). Dur("duration", duration). Str("status", statusCode.String()). Bool("is_client_stream", info.IsClientStream). Bool("is_server_stream", info.IsServerStream). Err(err). Msg("gRPC stream") return err } } `, 'internal/interceptors/validation.go': `package interceptors import ( "context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // Validator interface that proto messages with validation implement type Validator interface { Validate() error } func ValidationUnaryInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { // Check if request implements Validator if validator, ok := req.(Validator); ok { if err := validator.Validate(); err != nil { return nil, status.Errorf(codes.InvalidArgument, "validation failed: %v", err) } } return handler(ctx, req) } } `, 'internal/interceptors/recovery.go': `package interceptors import ( "context" "runtime/debug" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func RecoveryUnaryInterceptor(logger *zerolog.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { defer func() { if r := recover(); r != nil { logger.Error(). Interface("panic", r). Str("stack", string(debug.Stack())). Str("method", info.FullMethod). Msg("Recovered from panic") err = status.Errorf(codes.Internal, "internal server error") } }() return handler(ctx, req) } } func RecoveryStreamInterceptor(logger *zerolog.Logger) grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) { defer func() { if r := recover(); r != nil { logger.Error(). Interface("panic", r). Str("stack", string(debug.Stack())). Str("method", info.FullMethod). Msg("Recovered from panic in stream") err = status.Errorf(codes.Internal, "internal server error") } }() return handler(srv, ss) } } `, 'internal/interceptors/metrics.go': `package interceptors import ( "context" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "google.golang.org/grpc" "google.golang.org/grpc/status" ) var ( grpcDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "grpc_request_duration_seconds", Help: "Duration of gRPC requests in seconds", }, []string{"method", "status"}) grpcRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "grpc_requests_total", Help: "Total number of gRPC requests", }, []string{"method", "status"}) ) func MetricsUnaryInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { start := time.Now() resp, err := handler(ctx, req) statusCode := "OK" if err != nil { if s, ok := status.FromError(err); ok { statusCode = s.Code().String() } } duration := time.Since(start).Seconds() grpcDuration.WithLabelValues(info.FullMethod, statusCode).Observe(duration) grpcRequests.WithLabelValues(info.FullMethod, statusCode).Inc() return resp, err } } func MetricsStreamInterceptor() grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { start := time.Now() err := handler(srv, ss) statusCode := "OK" if err != nil { if s, ok := status.FromError(err); ok { statusCode = s.Code().String() } } duration := time.Since(start).Seconds() grpcDuration.WithLabelValues(info.FullMethod, statusCode).Observe(duration) grpcRequests.WithLabelValues(info.FullMethod, statusCode).Inc() return err } } `, // Database 'database/database.go': `package database import ( "fmt" "{{projectName}}/models" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func Initialize(databaseURL string) (*gorm.DB, error) { var db *gorm.DB var err error // Parse database URL to determine driver switch { case contains(databaseURL, "postgres://") || contains(databaseURL, "postgresql://"): db, err = gorm.Open(postgres.Open(databaseURL), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) case contains(databaseURL, "mysql://"): db, err = gorm.Open(mysql.Open(databaseURL), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) case contains(databaseURL, "sqlite://") || databaseURL == ":memory:": db, err = gorm.Open(sqlite.Open(databaseURL), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) default: return nil, fmt.Errorf("unsupported database URL: %s", databaseURL) } if err != nil { return nil, fmt.Errorf("failed to connect to database: %w", err) } return db, nil } func Migrate(db *gorm.DB) error { return db.AutoMigrate( &models.User{}, &models.Product{}, ) } func contains(s, substr string) bool { return len(s) >= len(substr) && s[:len(substr)] == substr } `, // Tests 'internal/services/user_service_test.go': `package services import ( "context" "testing" "{{projectName}}/models" pb "{{projectName}}/proto/gen" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func setupTestDB(t *testing.T) *gorm.DB { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) err = db.AutoMigrate(&models.User{}) require.NoError(t, err) return db } func TestUserService_CreateUser(t *testing.T) { db := setupTestDB(t) logger := zerolog.Nop() service := NewUserService(db, &logger) ctx := context.Background() req := &pb.CreateUserRequest{ Email: "test@example.com", Password: "password123", Name: "Test User", Role: pb.UserRole_USER_ROLE_USER, } user, err := service.CreateUser(ctx, req) require.NoError(t, err) assert.NotEmpty(t, user.Id) assert.Equal(t, req.Email, user.Email) assert.Equal(t, req.Name, user.Name) // Test duplicate email _, err = service.CreateUser(ctx, req) require.Error(t, err) st, ok := status.FromError(err) require.True(t, ok) assert.Equal(t, codes.AlreadyExists, st.Code()) } func TestUserService_GetUser(t *testing.T) { db := setupTestDB(t) logger := zerolog.Nop() service := NewUserService(db, &logger) // Create a user first testUser := &models.User{ Email: "test@example.com", Name: "Test User", Role: "USER_ROLE_USER", } testUser.SetPassword("password123") db.Create(testUser) ctx := context.Background() req := &pb.GetUserRequest{ Id: testUser.ID, } user, err := service.GetUser(ctx, req) require.NoError(t, err) assert.Equal(t, testUser.ID, user.Id) assert.Equal(t, testUser.Email, user.Email) // Test non-existent user req.Id = "non-existent-id" _, err = service.GetUser(ctx, req) require.Error(t, err) st, ok := status.FromError(err) require.True(t, ok) assert.Equal(t, codes.NotFound, st.Code()) } func TestUserService_Login(t *testing.T) { db := setupTestDB(t) logger := zerolog.Nop() service := NewUserService(db, &logger) // Create a user first testUser := &models.User{ Email: "test@example.com", Name: "Test User", Role: "USER_ROLE_USER", Active: true, } testUser.SetPassword("password123") db.Create(testUser) ctx := context.Background() req := &pb.LoginRequest{ Email: "test@example.com", Password: "password123", } resp, err := service.Login(ctx, req) require.NoError(t, err) assert.NotEmpty(t, resp.AccessToken) assert.NotEmpty(t, resp.RefreshToken) assert.Equal(t, testUser.ID, resp.User.Id) // Test invalid password req.Password = "wrongpassword" _, err = service.Login(ctx, req) require.Error(t, err) st, ok := status.FromError(err) require.True(t, ok) assert.Equal(t, codes.Unauthenticated, st.Code()) // Test inactive user testUser.Active = false db.Save(testUser) req.Password = "password123" _, err = service.Login(ctx, req) require.Error(t, err) st, ok = status.FromError(err) require.True(t, ok) assert.Equal(t, codes.PermissionDenied, st.Code()) } `, // Docker configuration 'Dockerfile': `# Build stage FROM golang:1.21-alpine AS builder # Install build dependencies RUN apk add --no-cache git ca-certificates # Set working directory WORKDIR /app # Copy go mod files COPY go.mod go.sum ./ # Download dependencies RUN go mod download # Copy source code COPY . . # Build the application RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server . # Final stage FROM alpine:latest # Install runtime dependencies RUN apk --no-cache add ca-certificates tzdata # Create non-root user RUN addgroup -g 1000 -S app && \ adduser -u 1000 -S app -G app # Set working directory WORKDIR /app # Copy binary from builder COPY --from=builder /app/server . COPY --chown=app:app .env.example .env # Change ownership RUN chown -R app:app /app # Switch to non-root user USER app # Expose port EXPOSE 50051 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD grpc_health_probe -addr=:50051 || exit 1 # Run the application CMD ["./server"] `, 'docker-compose.yml': `version: '3.8' services: app: build: . ports: - "50051:50051" environment: - PORT=50051 - DATABASE_URL=postgres://postgres:password@postgres:5432/{{projectName}}?sslmode=disable - JWT_SECRET=your-secret-key - LOG_LEVEL=info - REDIS_URL=redis://redis:6379/0 depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - app-network restart: unless-stopped postgres: image: postgres:16-alpine ports: - "5432:5432" environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - POSTGRES_DB={{projectName}} volumes: - postgres-data:/var/lib/postgresql/data networks: - app-network healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data networks: - app-network healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' networks: - app-network volumes: postgres-data: redis-data: prometheus-data: networks: app-network: driver: bridge `, // Makefile 'Makefile': `.PHONY: all build run test clean proto lint fmt vet # Variables BINARY_NAME=server DOCKER_IMAGE={{projectName}}:latest GO_FILES=$(shell find . -name '*.go' -type f) PROTO_FILES=$(shell find proto -name '*.proto' -type f) # Build the application all: clean build build: @echo "Building..." go build -o $(BINARY_NAME) -v # Run the application run: build @echo "Running..." ./$(BINARY_NAME) # Run with hot reload dev: @echo "Running with hot reload..." air # Clean build artifacts clean: @echo "Cleaning..." go clean rm -f $(BINARY_NAME) rm -rf proto/gen # Run tests test: @echo "Running tests..." go test -v -race -cover ./... # Run tests with coverage test-coverage: @echo "Running tests with coverage..." go test -v -race -coverprofile=coverage.out ./... go tool cover -html=coverage.out -o coverage.html # Generate protobuf files proto: @echo "Generating protobuf files..." buf generate # Lint the code lint: @echo "Linting..." golangci-lint run # Format the code fmt: @echo "Formatting..." go fmt ./... # Run go vet vet: @echo "Vetting..." go vet ./... # Install dependencies deps: @echo "Installing dependencies..." go mod download go install github.com/cosmtrek/air@latest go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest go install github.com/bufbuild/buf/cmd/buf@latest go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest go install github.com/envoyproxy/protoc-gen-validate@latest # Docker commands docker-build: @echo "Building Docker image..." docker build -t $(DOCKER_IMAGE) . docker-run: @echo "Running Docker container..." docker run -p 50051:50051 $(DOCKER_IMAGE) docker-compose-up: @echo "Starting services with docker-compose..." docker-compose up -d docker-compose-down: @echo "Stopping services..." docker-compose down # Database migrations migrate-up: @echo "Running migrations..." go run main.go migrate up migrate-down: @echo "Rolling back migrations..." go run main.go migrate down # Security scan security: @echo "Running security scan..." gosec ./... # Performance profiling profile: @echo "Running CPU profile..." go test -cpuprofile cpu.prof -bench . go tool pprof cpu.prof # gRPC health check health: @echo "Checking gRPC health..." grpc_health_probe -addr=:50051 `, '.air.toml': `root = "." testdata_dir = "testdata" tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/main" cmd = "go build -o ./tmp/main ." delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata", "proto/gen", "docs"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] include_ext = ["go", "tpl", "tmpl", "html"] kill_delay = "0s" log = "build-errors.log" send_interrupt = false stop_on_error = true [color] app = "" build = "yellow" main = "magenta" runner = "green" watcher = "cyan" [log] time = false [misc] clean_on_exit = false [screen] clear_on_rebuild = false `, '.env.example': `# Server configuration PORT=50051 # Database configuration DATABASE_URL=postgres://user:password@localhost/dbname?sslmode=disable # DATABASE_URL=mysql://user:password@tcp(localhost:3306)/dbname # DATABASE_URL=sqlite:///path/to/database.db # JWT configuration JWT_SECRET=your-secret-key-change-this-in-production # Logging LOG_LEVEL=info # Redis configuration REDIS_URL=redis://localhost:6379/0 # Monitoring JAEGER_ENDPOINT=http://localhost:14268/api/traces `, '.gitignore': `# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with go test -c *.test # Output of the go coverage tool *.out coverage.html # Dependency directories vendor/ # Go workspace file go.work # Environment files .env .env.local .env.*.local # IDE files .idea/ .vscode/ *.swp *.swo *~ # OS files .DS_Store Thumbs.db # Build output server {{projectName}} /tmp /dist # Generated files proto/gen/ docs/openapi/ # Logs *.log # Profiling data *.prof *.pprof # Air tmp directory tmp/ `, 'prometheus.yml': `global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'grpc-server' static_configs: - targets: ['app:50051'] `, 'README.md': `# {{projectName}} High-performa