@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,614 lines (1,368 loc) • 40 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ginTemplate = void 0;
exports.ginTemplate = {
id: 'gin',
name: 'gin',
displayName: 'Gin Framework',
description: 'Fast HTTP web framework for Go with middleware support and JSON validation',
language: 'go',
framework: 'gin',
version: '1.9.1',
tags: ['go', 'gin', 'api', 'rest', 'middleware', 'performance'],
port: 8080,
dependencies: {},
features: ['authentication', 'validation', 'logging', 'cors', 'documentation'],
files: {
// Go module configuration
'go.mod': `module {{projectName}}
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/joho/godotenv v1.5.1
github.com/go-playground/validator/v10 v10.16.0
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.2
github.com/swaggo/files v1.0.1
github.com/sirupsen/logrus v1.9.3
github.com/gin-contrib/cors v1.5.0
github.com/gin-contrib/requestid v0.0.6
github.com/gin-contrib/gzip v0.0.6
github.com/ulule/limiter/v3 v3.11.2
github.com/redis/go-redis/v9 v9.3.1
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
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.2.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/bytedance/sonic v1.10.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/spec v0.20.13 // indirect
github.com/go-openapi/swag v0.22.7 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.5.0 // 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/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.6.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
golang.org/x/tools v0.16.1 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
`,
// Main application entry point
'main.go': `package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"{{projectName}}/config"
"{{projectName}}/database"
_ "{{projectName}}/docs" // swagger docs
"{{projectName}}/middleware"
"{{projectName}}/routes"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
// @title {{projectName}} API
// @version 1.0
// @description API server for {{projectName}} built with Gin framework
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
func main() {
// Load environment variables
if err := godotenv.Load(); err != nil {
log.Println("No .env file found")
}
// Initialize configuration
cfg := config.New()
// Set Gin mode
if cfg.Environment == "production" {
gin.SetMode(gin.ReleaseMode)
}
// Initialize database
db, err := database.Initialize(cfg)
if err != nil {
log.Fatal("Failed to initialize database:", err)
}
// Run migrations
if err := database.Migrate(db); err != nil {
log.Fatal("Failed to run migrations:", err)
}
// Create Gin router
router := gin.New()
// Global middleware
router.Use(middleware.Logger())
router.Use(middleware.Recovery())
router.Use(middleware.RequestID())
router.Use(middleware.CORS(cfg))
router.Use(middleware.Gzip())
router.Use(middleware.RateLimiter(cfg))
// Health check
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"time": time.Now().UTC(),
})
})
// Swagger documentation
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
// API routes
api := router.Group("/api/v1")
{
routes.RegisterAuthRoutes(api, db, cfg)
routes.RegisterUserRoutes(api, db, cfg)
routes.RegisterProductRoutes(api, db)
}
// Create HTTP server
srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Port),
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Start server in goroutine
go func() {
log.Printf("Starting server on port %d", cfg.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Failed to start server: %v", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// Graceful shutdown with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
`,
// Configuration
'config/config.go': `package config
import (
"os"
"strconv"
"time"
)
type Config struct {
Environment string
Port int
// Database
DBHost string
DBPort int
DBUser string
DBPassword string
DBName string
DBSSLMode string
// JWT
JWTSecret string
JWTExpirationHours int
// Redis
RedisAddr string
RedisPassword string
RedisDB int
// Rate limiting
RateLimitRequests int
RateLimitDuration time.Duration
// CORS
AllowedOrigins []string
}
func New() *Config {
return &Config{
Environment: getEnv("ENVIRONMENT", "development"),
Port: getEnvAsInt("PORT", 8080),
// Database
DBHost: getEnv("DB_HOST", "localhost"),
DBPort: getEnvAsInt("DB_PORT", 5432),
DBUser: getEnv("DB_USER", "postgres"),
DBPassword: getEnv("DB_PASSWORD", "password"),
DBName: getEnv("DB_NAME", "{{projectName}}"),
DBSSLMode: getEnv("DB_SSLMODE", "disable"),
// JWT
JWTSecret: getEnv("JWT_SECRET", "your-secret-key"),
JWTExpirationHours: getEnvAsInt("JWT_EXPIRATION_HOURS", 24),
// Redis
RedisAddr: getEnv("REDIS_ADDR", "localhost:6379"),
RedisPassword: getEnv("REDIS_PASSWORD", ""),
RedisDB: getEnvAsInt("REDIS_DB", 0),
// Rate limiting
RateLimitRequests: getEnvAsInt("RATE_LIMIT_REQUESTS", 100),
RateLimitDuration: time.Duration(getEnvAsInt("RATE_LIMIT_DURATION_MINUTES", 1)) * time.Minute,
// CORS
AllowedOrigins: []string{getEnv("ALLOWED_ORIGINS", "*")},
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvAsInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}
`,
// Database package
'database/database.go': `package database
import (
"fmt"
"log"
"{{projectName}}/config"
"{{projectName}}/models"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func Initialize(cfg *config.Config) (*gorm.DB, error) {
var dialector gorm.Dialector
switch cfg.DBHost {
case "sqlite", ":memory:":
dialector = sqlite.Open(cfg.DBName)
case "mysql":
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
cfg.DBUser, cfg.DBPassword, cfg.DBHost, cfg.DBPort, cfg.DBName)
dialector = mysql.Open(dsn)
default:
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBPassword, cfg.DBName, cfg.DBSSLMode)
dialector = postgres.Open(dsn)
}
// Configure GORM
gormConfig := &gorm.Config{}
if cfg.Environment == "development" {
gormConfig.Logger = logger.Default.LogMode(logger.Info)
} else {
gormConfig.Logger = logger.Default.LogMode(logger.Silent)
}
db, err := gorm.Open(dialector, gormConfig)
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
log.Println("Database connection established")
return db, nil
}
func Migrate(db *gorm.DB) error {
return db.AutoMigrate(
&models.User{},
&models.Product{},
)
}
`,
// Models
'models/user.go': `package models
import (
"time"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
type User struct {
ID uint \`gorm:"primarykey" json:"id"\`
CreatedAt time.Time \`json:"created_at"\`
UpdatedAt time.Time \`json:"updated_at"\`
DeletedAt gorm.DeletedAt \`gorm:"index" json:"-"\`
Email string \`gorm:"uniqueIndex;not null" json:"email" binding:"required,email"\`
Password string \`gorm:"not null" json:"-"\`
Name string \`gorm:"not null" json:"name" binding:"required,min=2,max=100"\`
Role string \`gorm:"default:user" json:"role"\`
Active bool \`gorm:"default:true" json:"active"\`
}
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
}
type LoginRequest struct {
Email string \`json:"email" binding:"required,email"\`
Password string \`json:"password" binding:"required,min=6"\`
}
type RegisterRequest struct {
Email string \`json:"email" binding:"required,email"\`
Password string \`json:"password" binding:"required,min=6"\`
Name string \`json:"name" binding:"required,min=2,max=100"\`
}
type UserResponse struct {
ID uint \`json:"id"\`
Email string \`json:"email"\`
Name string \`json:"name"\`
Role string \`json:"role"\`
Active bool \`json:"active"\`
CreatedAt time.Time \`json:"created_at"\`
}
func (u *User) ToResponse() UserResponse {
return UserResponse{
ID: u.ID,
Email: u.Email,
Name: u.Name,
Role: u.Role,
Active: u.Active,
CreatedAt: u.CreatedAt,
}
}
`,
'models/product.go': `package models
import (
"time"
"gorm.io/gorm"
)
type Product struct {
ID uint \`gorm:"primarykey" json:"id"\`
CreatedAt time.Time \`json:"created_at"\`
UpdatedAt time.Time \`json:"updated_at"\`
DeletedAt gorm.DeletedAt \`gorm:"index" json:"-"\`
Name string \`gorm:"not null" json:"name" binding:"required,min=1,max=200"\`
Description string \`json:"description"\`
Price float64 \`gorm:"not null" json:"price" binding:"required,min=0"\`
Stock int \`gorm:"default:0" json:"stock" binding:"min=0"\`
Active bool \`gorm:"default:true" json:"active"\`
}
type CreateProductRequest struct {
Name string \`json:"name" binding:"required,min=1,max=200"\`
Description string \`json:"description"\`
Price float64 \`json:"price" binding:"required,min=0"\`
Stock int \`json:"stock" binding:"min=0"\`
}
type UpdateProductRequest struct {
Name *string \`json:"name,omitempty" binding:"omitempty,min=1,max=200"\`
Description *string \`json:"description,omitempty"\`
Price *float64 \`json:"price,omitempty" binding:"omitempty,min=0"\`
Stock *int \`json:"stock,omitempty" binding:"omitempty,min=0"\`
Active *bool \`json:"active,omitempty"\`
}
`,
// Middleware
'middleware/auth.go': `package middleware
import (
"net/http"
"strings"
"{{projectName}}/config"
"{{projectName}}/utils"
"github.com/gin-gonic/gin"
)
func Auth(cfg *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
c.Abort()
return
}
bearerToken := strings.Split(authHeader, " ")
if len(bearerToken) != 2 || bearerToken[0] != "Bearer" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"})
c.Abort()
return
}
token := bearerToken[1]
claims, err := utils.ValidateJWT(token, cfg.JWTSecret)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
c.Abort()
return
}
// Set user info in context
c.Set("userID", claims.UserID)
c.Set("userEmail", claims.Email)
c.Set("userRole", claims.Role)
c.Next()
}
}
func RequireRole(roles ...string) gin.HandlerFunc {
return func(c *gin.Context) {
userRole, exists := c.Get("userRole")
if !exists {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
c.Abort()
return
}
roleStr, ok := userRole.(string)
if !ok {
c.JSON(http.StatusForbidden, gin.H{"error": "Invalid role"})
c.Abort()
return
}
// Check if user has required role
for _, role := range roles {
if roleStr == role {
c.Next()
return
}
}
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
c.Abort()
}
}
`,
'middleware/cors.go': `package middleware
import (
"{{projectName}}/config"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func CORS(cfg *config.Config) gin.HandlerFunc {
config := cors.Config{
AllowOrigins: cfg.AllowedOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "Accept", "X-Request-ID"},
ExposeHeaders: []string{"Content-Length", "X-Request-ID"},
AllowCredentials: true,
MaxAge: 12 * 3600, // 12 hours
}
return cors.New(config)
}
`,
'middleware/logger.go': `package middleware
import (
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func Logger() gin.HandlerFunc {
logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{})
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
logger.WithFields(logrus.Fields{
"timestamp": param.TimeStamp.Format(time.RFC3339),
"status_code": param.StatusCode,
"latency": param.Latency,
"client_ip": param.ClientIP,
"method": param.Method,
"path": param.Path,
"request_id": param.Request.Header.Get("X-Request-ID"),
"error": param.ErrorMessage,
}).Info("HTTP Request")
return ""
})
}
func Recovery() gin.HandlerFunc {
logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{})
return gin.RecoveryWithWriter(logger.Out)
}
`,
'middleware/rate_limiter.go': `package middleware
import (
"net/http"
"{{projectName}}/config"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
"github.com/ulule/limiter/v3"
"github.com/ulule/limiter/v3/drivers/store/redis"
)
func RateLimiter(cfg *config.Config) gin.HandlerFunc {
// Create Redis client
client := redis.NewClient(&redis.Options{
Addr: cfg.RedisAddr,
Password: cfg.RedisPassword,
DB: cfg.RedisDB,
})
// Create store
store, err := redis.NewStoreWithOptions(client, limiter.StoreOptions{
Prefix: "rate_limit",
})
if err != nil {
// Fallback to memory store if Redis fails
store = limiter.NewMemoryStore()
}
// Create rate limiter
rate := limiter.Rate{
Period: cfg.RateLimitDuration,
Limit: int64(cfg.RateLimitRequests),
}
instance := limiter.New(store, rate)
return func(c *gin.Context) {
context, err := instance.Get(c.Request.Context(), c.ClientIP())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Rate limiter error"})
c.Abort()
return
}
// Set rate limit headers
c.Header("X-RateLimit-Limit", string(context.Limit))
c.Header("X-RateLimit-Remaining", string(context.Remaining))
c.Header("X-RateLimit-Reset", string(context.Reset))
if context.Reached {
c.JSON(http.StatusTooManyRequests, gin.H{"error": "Rate limit exceeded"})
c.Abort()
return
}
c.Next()
}
}
`,
'middleware/common.go': `package middleware
import (
"github.com/gin-contrib/gzip"
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
)
func RequestID() gin.HandlerFunc {
return requestid.New()
}
func Gzip() gin.HandlerFunc {
return gzip.Gzip(gzip.DefaultCompression)
}
`,
// Routes
'routes/auth.go': `package routes
import (
"net/http"
"{{projectName}}/config"
"{{projectName}}/models"
"{{projectName}}/utils"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func RegisterAuthRoutes(router *gin.RouterGroup, db *gorm.DB, cfg *config.Config) {
auth := router.Group("/auth")
{
auth.POST("/register", register(db))
auth.POST("/login", login(db, cfg))
}
}
// @Summary Register a new user
// @Description Create a new user account
// @Tags auth
// @Accept json
// @Produce json
// @Param user body models.RegisterRequest true "User registration data"
// @Success 201 {object} models.UserResponse
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /auth/register [post]
func register(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var req models.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Check if user exists
var existingUser models.User
if err := db.Where("email = ?", req.Email).First(&existingUser).Error; err == nil {
c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"})
return
}
// Create new user
user := models.User{
Email: req.Email,
Name: req.Name,
}
if err := user.SetPassword(req.Password); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
return
}
if err := db.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
return
}
c.JSON(http.StatusCreated, user.ToResponse())
}
}
// @Summary Login user
// @Description Authenticate user and return JWT token
// @Tags auth
// @Accept json
// @Produce json
// @Param credentials body models.LoginRequest true "Login credentials"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Router /auth/login [post]
func login(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
var req models.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Find user
var user models.User
if err := db.Where("email = ?", req.Email).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
return
}
// Check password
if !user.CheckPassword(req.Password) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
return
}
// Check if user is active
if !user.Active {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Account is disabled"})
return
}
// Generate JWT token
token, err := utils.GenerateJWT(user.ID, user.Email, user.Role, cfg.JWTSecret, cfg.JWTExpirationHours)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
"user": user.ToResponse(),
})
}
}
`,
'routes/user.go': `package routes
import (
"net/http"
"strconv"
"{{projectName}}/config"
"{{projectName}}/middleware"
"{{projectName}}/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func RegisterUserRoutes(router *gin.RouterGroup, db *gorm.DB, cfg *config.Config) {
users := router.Group("/users")
users.Use(middleware.Auth(cfg))
{
users.GET("", middleware.RequireRole("admin"), listUsers(db))
users.GET("/:id", getUser(db))
users.GET("/me", getCurrentUser(db))
users.PUT("/me", updateCurrentUser(db))
users.DELETE("/:id", middleware.RequireRole("admin"), deleteUser(db))
}
}
// @Summary List all users
// @Description Get a list of all users (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {array} models.UserResponse
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Router /users [get]
func listUsers(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var users []models.User
if err := db.Find(&users).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
return
}
response := make([]models.UserResponse, len(users))
for i, user := range users {
response[i] = user.ToResponse()
}
c.JSON(http.StatusOK, response)
}
}
// @Summary Get user by ID
// @Description Get a specific user by ID
// @Tags users
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "User ID"
// @Success 200 {object} models.UserResponse
// @Failure 401 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /users/{id} [get]
func getUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
var user models.User
if err := db.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, user.ToResponse())
}
}
// @Summary Get current user
// @Description Get the currently authenticated user
// @Tags users
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} models.UserResponse
// @Failure 401 {object} map[string]string
// @Router /users/me [get]
func getCurrentUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
var user models.User
if err := db.First(&user, userID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, user.ToResponse())
}
}
// @Summary Update current user
// @Description Update the currently authenticated user's profile
// @Tags users
// @Accept json
// @Produce json
// @Security Bearer
// @Param user body map[string]string true "User update data"
// @Success 200 {object} models.UserResponse
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Router /users/me [put]
func updateCurrentUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
var user models.User
if err := db.First(&user, userID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
var updates map[string]interface{}
if err := c.ShouldBindJSON(&updates); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Remove fields that shouldn't be updated
delete(updates, "id")
delete(updates, "email")
delete(updates, "role")
delete(updates, "password")
if err := db.Model(&user).Updates(updates).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user"})
return
}
c.JSON(http.StatusOK, user.ToResponse())
}
}
// @Summary Delete user
// @Description Delete a user (admin only)
// @Tags users
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "User ID"
// @Success 204
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /users/{id} [delete]
func deleteUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
result := db.Delete(&models.User{}, id)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user"})
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
c.Status(http.StatusNoContent)
}
}
`,
'routes/product.go': `package routes
import (
"net/http"
"strconv"
"{{projectName}}/middleware"
"{{projectName}}/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func RegisterProductRoutes(router *gin.RouterGroup, db *gorm.DB) {
products := router.Group("/products")
{
products.GET("", listProducts(db))
products.GET("/:id", getProduct(db))
products.POST("", middleware.RequireRole("admin"), createProduct(db))
products.PUT("/:id", middleware.RequireRole("admin"), updateProduct(db))
products.DELETE("/:id", middleware.RequireRole("admin"), deleteProduct(db))
}
}
// @Summary List all products
// @Description Get a list of all products with pagination
// @Tags products
// @Accept json
// @Produce json
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Items per page" default(10)
// @Success 200 {object} map[string]interface{}
// @Router /products [get]
func listProducts(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
offset := (page - 1) * limit
var products []models.Product
var total int64
db.Model(&models.Product{}).Where("active = ?", true).Count(&total)
if err := db.Where("active = ?", true).Offset(offset).Limit(limit).Find(&products).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch products"})
return
}
c.JSON(http.StatusOK, gin.H{
"data": products,
"total": total,
"page": page,
"limit": limit,
})
}
}
// @Summary Get product by ID
// @Description Get a specific product by ID
// @Tags products
// @Accept json
// @Produce json
// @Param id path int true "Product ID"
// @Success 200 {object} models.Product
// @Failure 404 {object} map[string]string
// @Router /products/{id} [get]
func getProduct(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})
return
}
var product models.Product
if err := db.First(&product, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
return
}
c.JSON(http.StatusOK, product)
}
}
// @Summary Create a new product
// @Description Create a new product (admin only)
// @Tags products
// @Accept json
// @Produce json
// @Security Bearer
// @Param product body models.CreateProductRequest true "Product data"
// @Success 201 {object} models.Product
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Router /products [post]
func createProduct(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var req models.CreateProductRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
product := models.Product{
Name: req.Name,
Description: req.Description,
Price: req.Price,
Stock: req.Stock,
Active: true,
}
if err := db.Create(&product).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create product"})
return
}
c.JSON(http.StatusCreated, product)
}
}
// @Summary Update product
// @Description Update a product (admin only)
// @Tags products
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "Product ID"
// @Param product body models.UpdateProductRequest true "Product update data"
// @Success 200 {object} models.Product
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /products/{id} [put]
func updateProduct(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})
return
}
var product models.Product
if err := db.First(&product, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
return
}
var req models.UpdateProductRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Update fields
if req.Name != nil {
product.Name = *req.Name
}
if req.Description != nil {
product.Description = *req.Description
}
if req.Price != nil {
product.Price = *req.Price
}
if req.Stock != nil {
product.Stock = *req.Stock
}
if req.Active != nil {
product.Active = *req.Active
}
if err := db.Save(&product).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update product"})
return
}
c.JSON(http.StatusOK, product)
}
}
// @Summary Delete product
// @Description Delete a product (admin only)
// @Tags products
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "Product ID"
// @Success 204
// @Failure 401 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /products/{id} [delete]
func deleteProduct(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})
return
}
result := db.Delete(&models.Product{}, id)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete product"})
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
return
}
c.Status(http.StatusNoContent)
}
}
`,
// Utils
'utils/jwt.go': `package utils
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
type JWTClaims struct {
UserID uint \`json:"user_id"\`
Email string \`json:"email"\`
Role string \`json:"role"\`
jwt.RegisteredClaims
}
func GenerateJWT(userID uint, email, role, secret string, expirationHours int) (string, error) {
claims := JWTClaims{
UserID: userID,
Email: email,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(expirationHours))),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
func ValidateJWT(tokenString, secret string) (*JWTClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(secret), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*JWTClaims)
if !ok || !token.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}
`,
// Environment file
'.env.example': `# Environment
ENVIRONMENT=development
# Server
PORT=8080
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME={{projectName}}
DB_SSLMODE=disable
# JWT
JWT_SECRET=your-secret-key-change-this
JWT_EXPIRATION_HOURS=24
# Redis
REDIS_ADDR=localhost:6379
REDIS_PASSWORD=
REDIS_DB=0
# Rate Limiting
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_DURATION_MINUTES=1
# CORS
ALLOWED_ORIGINS=*
`,
// Docker files
'Dockerfile': `# Build stage
FROM golang:1.21-alpine AS builder
# Install build dependencies
RUN apk add --no-cache git
# 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 main .
# Generate swagger docs
RUN go install github.com/swaggo/swag/cmd/swag@latest
RUN swag init
# Final stage
FROM alpine:latest
# Install runtime dependencies
RUN apk --no-cache add ca-certificates tzdata
# Set working directory
WORKDIR /root/
# Copy binary from builder
COPY --from=builder /app/main .
COPY --from=builder /app/docs ./docs
# Copy .env file if needed
COPY --from=builder /app/.env.example .env
# Expose port
EXPOSE 8080
# Run the application
CMD ["./main"]
`,
'docker-compose.yml': `version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- ENVIRONMENT=development
- DB_HOST=postgres
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=password
- DB_NAME={{projectName}}
- REDIS_ADDR=redis:6379
depends_on:
- postgres
- redis
volumes:
- .:/app
command: air
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB={{projectName}}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
`,
// Air configuration for hot reload
'.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", "docs"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true
`,
// Makefile
'Makefile': `# Variables
APP_NAME={{projectName}}
MAIN_FILE=main.go
DOCKER_IMAGE={{projectName}}:latest
# Go commands
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
GOMOD=$(GOCMD) mod
# Build the application
build:
$(GOBUILD) -o $(APP_NAME) -v $(MAIN_FILE)
# Run the application
run:
$(GOCMD) run $(MAIN_FILE)
# Run with hot reload
dev:
air
# Test the application
test:
$(GOTEST) -v ./...
# Test with coverage
test-coverage:
$(GOTEST) -v -coverprofile=coverage.out ./...
$(GOCMD) tool cover -html=coverage.out
# Clean build artifacts
clean:
$(GOCLEAN)
rm -f $(APP_NAME)
rm -f coverage.out
# Download dependencies
deps:
$(GOMOD) download
$(GOMOD) tidy
# Update dependencies
deps-update:
$(GOGET) -u ./...
$(GOMOD) tidy
# Generate swagger documentation
swagger:
swag init
# Format code
fmt:
$(GOCMD) fmt ./...
# Lint code
lint:
golangci-lint run
# Docker build
docker-build:
docker build -t $(DOCKER_IMAGE) .
# Docker run
docker-run:
docker-compose up
# Docker stop
docker-stop:
docker-compose down
# Database migrations
migrate-up:
migrate -database "postgres://postgres:password@localhost:5432/$(APP_NAME)?sslmode=disable" -path db/migrations up
migrate-down:
migrate -database "postgres://postgres:password@localhost:5432/$(APP_NAME)?sslmode=disable" -path db/migrations down
# Install development tools
install-tools:
go install github.com/cosmtrek/air@latest
go install github.com/swaggo/swag/cmd/swag@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
.PHONY: build run dev test test-coverage clean deps deps-update swagger fmt lint docker-build docker-run docker-stop migrate-up migrate-down install-tools
`,
// README
'README.md': `# {{projectName}}
A high-performance REST API built with Gin framework in Go.
## Features
- **Gin Framework**: Fast HTTP web framework
- **JWT Authentication**: Secure token-based authentication
- **GORM ORM**: Database abstraction with migrations
- **Swagger Documentation**: Auto-generated API documentation
- **Rate Limiting**: Request throttling with Redis
- **Structured Logging**: JSON logs with Logrus
- **CORS Support**: Configurable cross-origin requests
- **Hot Reload**: Development with Air
- **Docker Support**: Containerized deployment
- **Testing**: Unit and integration tests
## Requirements
- Go 1.21+
- PostgreSQL (or MySQL/SQLite)
- Redis (for rate limiting)
- Docker (optional)
## Quick Start
1. Clone the repository
2. Copy \`.env.example\` to \`.env\` and configure
3. Install dependencies:
\`\`\`bash
make deps
\`\`\`
4. Run with hot reload:
\`\`\`bash
make dev
\`\`\`
5. Or run with Docker:
\`\`\`bash
make docker-run
\`\`\`
## API Documentation
Once the server is running, visit:
- Swagger UI: http://localhost:8080/swagger/index.html
## Development
### Install tools
\`\`\`bash
make install-tools
\`\`\`
### Run tests
\`\`\`bash
make test
\`\`\`
### Generate Swagger docs
\`\`\`bash
make swagger
\`\`\`
### Format code
\`\`\`bash
make fmt
\`\`\`
### Lint code
\`\`\`bash
make lint
\`\`\`
## Project Structure
\`\`\`
.
├── config/ # Configuration
├── database/ # Database connection and migrations
├── docs/ # Swagger documentation
├── middleware/ # HTTP middleware
├── models/ # Data models
├── routes/ # API routes
├── utils/ # Utility functions
├── main.go # Application entry point
├── Dockerfile # Docker configuration
├── docker-compose.yml
├── Makefile # Build automation
└── README.md
\`\`\`
`
}
};