1315 lines
37 KiB
Go
1315 lines
37 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// HandlerData contains template data for handler generation
|
|
type HandlerData struct {
|
|
Name string
|
|
NameLower string
|
|
NamePlural string
|
|
Category string
|
|
CategoryPath string
|
|
ModuleName string
|
|
TableName string
|
|
HasGet bool
|
|
HasPost bool
|
|
HasPut bool
|
|
HasDelete bool
|
|
HasStats bool
|
|
HasFilter bool
|
|
HasPagination bool
|
|
Timestamp string
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Println("Usage: go run generate-handler.go [category/]entity [methods]")
|
|
fmt.Println("Examples:")
|
|
fmt.Println(" go run generate-handler.go product get post")
|
|
fmt.Println(" go run generate-handler.go reference/peserta get post put delete")
|
|
fmt.Println(" go run generate-handler.go master/wilayah get")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Parse entity path (could be "entity" or "category/entity")
|
|
entityPath := os.Args[1]
|
|
methods := []string{}
|
|
if len(os.Args) > 2 {
|
|
methods = os.Args[2:]
|
|
} else {
|
|
// Default methods if none specified
|
|
methods = []string{"get", "post", "put", "delete"}
|
|
}
|
|
|
|
// Parse category and entity
|
|
var category, entityName string
|
|
if strings.Contains(entityPath, "/") {
|
|
parts := strings.Split(entityPath, "/")
|
|
if len(parts) != 2 {
|
|
fmt.Println("❌ Error: Invalid path format. Use 'category/entity' or just 'entity'")
|
|
os.Exit(1)
|
|
}
|
|
category = parts[0]
|
|
entityName = parts[1]
|
|
} else {
|
|
category = ""
|
|
entityName = entityPath
|
|
}
|
|
|
|
// Format names
|
|
entityName = strings.Title(entityName) // PascalCase entity name
|
|
entityLower := strings.ToLower(entityName)
|
|
entityPlural := entityLower + "s"
|
|
|
|
// Table name: include category if exists
|
|
var tableName string
|
|
if category != "" {
|
|
tableName = "data_" + category + "_" + entityLower
|
|
} else {
|
|
tableName = "data_" + entityLower
|
|
}
|
|
|
|
data := HandlerData{
|
|
Name: entityName,
|
|
NameLower: entityLower,
|
|
NamePlural: entityPlural,
|
|
Category: category,
|
|
CategoryPath: category,
|
|
ModuleName: "api-service",
|
|
TableName: tableName,
|
|
HasPagination: true,
|
|
HasFilter: true,
|
|
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
|
}
|
|
|
|
// Set methods based on arguments
|
|
for _, m := range methods {
|
|
switch strings.ToLower(m) {
|
|
case "get":
|
|
data.HasGet = true
|
|
case "post":
|
|
data.HasPost = true
|
|
case "put":
|
|
data.HasPut = true
|
|
case "delete":
|
|
data.HasDelete = true
|
|
case "stats":
|
|
data.HasStats = true
|
|
}
|
|
}
|
|
|
|
// Always add stats if we have get
|
|
if data.HasGet {
|
|
data.HasStats = true
|
|
}
|
|
|
|
// Create directories with improved logic
|
|
var handlerDir, modelDir string
|
|
|
|
if category != "" {
|
|
// Dengan kategori: internal/handlers/category/
|
|
handlerDir = filepath.Join("internal", "handlers", category)
|
|
modelDir = filepath.Join("internal", "models", category)
|
|
} else {
|
|
// Tanpa kategori: langsung internal/handlers/
|
|
handlerDir = filepath.Join("internal", "handlers")
|
|
modelDir = filepath.Join("internal", "models")
|
|
}
|
|
|
|
// Buat direktori
|
|
for _, d := range []string{handlerDir, modelDir} {
|
|
if err := os.MkdirAll(d, 0755); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Generate files
|
|
generateHandlerFile(data, handlerDir)
|
|
generateModelFile(data, modelDir)
|
|
updateRoutesFile(data)
|
|
|
|
fmt.Printf("✅ Successfully generated handler: %s\n", entityName)
|
|
if category != "" {
|
|
fmt.Printf("📁 Category: %s\n", category)
|
|
}
|
|
fmt.Printf("📁 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go"))
|
|
fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go"))
|
|
}
|
|
|
|
// ================= HANDLER GENERATION =====================
|
|
func generateHandlerFile(data HandlerData, handlerDir string) {
|
|
// Build import path based on category
|
|
var modelsImportPath string
|
|
if data.Category != "" {
|
|
modelsImportPath = data.ModuleName + "/internal/models/" + data.Category
|
|
} else {
|
|
modelsImportPath = data.ModuleName + "/internal/models"
|
|
}
|
|
|
|
handlerContent := `package handlers
|
|
|
|
import (
|
|
"` + data.ModuleName + `/internal/config"
|
|
"` + data.ModuleName + `/internal/database"
|
|
models "` + modelsImportPath + `"
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var (
|
|
db database.Service
|
|
once sync.Once
|
|
validate *validator.Validate
|
|
)
|
|
|
|
// Initialize the database connection and validator
|
|
func init() {
|
|
once.Do(func() {
|
|
db = database.New(config.LoadConfig())
|
|
validate = validator.New()
|
|
// Register custom validations if needed
|
|
validate.RegisterValidation("` + data.NameLower + `_status", validate` + data.Name + `Status)
|
|
if db == nil {
|
|
log.Fatal("Failed to initialize database connection")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Custom validation for ` + data.NameLower + ` status
|
|
func validate` + data.Name + `Status(fl validator.FieldLevel) bool {
|
|
return models.IsValidStatus(fl.Field().String())
|
|
}
|
|
|
|
// ` + data.Name + `Handler handles ` + data.NameLower + ` services
|
|
type ` + data.Name + `Handler struct {
|
|
db database.Service
|
|
}
|
|
|
|
// New` + data.Name + `Handler creates a new ` + data.Name + `Handler
|
|
func New` + data.Name + `Handler() *` + data.Name + `Handler {
|
|
return &` + data.Name + `Handler{
|
|
db: db,
|
|
}
|
|
}`
|
|
|
|
// Add methods
|
|
if data.HasGet {
|
|
handlerContent += generateGetMethods(data)
|
|
}
|
|
|
|
if data.HasPost {
|
|
handlerContent += generateCreateMethod(data)
|
|
}
|
|
|
|
if data.HasPut {
|
|
handlerContent += generateUpdateMethod(data)
|
|
}
|
|
|
|
if data.HasDelete {
|
|
handlerContent += generateDeleteMethod(data)
|
|
}
|
|
|
|
if data.HasStats {
|
|
handlerContent += generateStatsMethod(data)
|
|
}
|
|
|
|
// Add helper methods
|
|
handlerContent += generateHelperMethods(data)
|
|
writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent)
|
|
}
|
|
|
|
func generateGetMethods(data HandlerData) string {
|
|
// Build route path based on category
|
|
var routePath, singleRoutePath string
|
|
if data.Category != "" {
|
|
routePath = data.Category + "/" + data.NamePlural
|
|
singleRoutePath = data.Category + "/" + data.NameLower
|
|
} else {
|
|
routePath = data.NamePlural
|
|
singleRoutePath = data.NameLower
|
|
}
|
|
|
|
return `
|
|
|
|
// Get` + data.Name + ` godoc
|
|
// @Summary Get ` + data.NameLower + ` with pagination and optional aggregation
|
|
// @Description Returns a paginated list of ` + data.NamePlural + ` with optional summary statistics
|
|
// @Tags ` + data.NameLower + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param limit query int false "Limit (max 100)" default(10)
|
|
// @Param offset query int false "Offset" default(0)
|
|
// @Param include_summary query bool false "Include aggregation summary" default(false)
|
|
// @Param status query string false "Filter by status"
|
|
// @Param search query string false "Search in multiple fields"
|
|
// @Success 200 {object} models.` + data.Name + `GetResponse "Success response"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/` + routePath + ` [get]
|
|
func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) {
|
|
// Parse pagination parameters
|
|
limit, offset, err := h.parsePaginationParams(c)
|
|
if err != nil {
|
|
h.respondError(c, "Invalid pagination parameters", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Parse filter parameters
|
|
filter := h.parseFilterParams(c)
|
|
includeAggregation := c.Query("include_summary") == "true"
|
|
|
|
// Get database connection
|
|
dbConn, err := h.db.GetDB("satudata")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Create context with timeout
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Execute concurrent operations
|
|
var (
|
|
items []models.` + data.Name + `
|
|
total int
|
|
aggregateData *models.AggregateData
|
|
wg sync.WaitGroup
|
|
errChan = make(chan error, 3)
|
|
mu sync.Mutex
|
|
)
|
|
|
|
// Fetch total count
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := h.getTotalCount(ctx, dbConn, filter, &total); err != nil {
|
|
mu.Lock()
|
|
errChan <- fmt.Errorf("failed to get total count: %w", err)
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
// Fetch main data
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
result, err := h.fetch` + data.Name + `s(ctx, dbConn, filter, limit, offset)
|
|
mu.Lock()
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("failed to fetch data: %w", err)
|
|
} else {
|
|
items = result
|
|
}
|
|
mu.Unlock()
|
|
}()
|
|
|
|
// Fetch aggregation data if requested
|
|
if includeAggregation {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
result, err := h.getAggregateData(ctx, dbConn, filter)
|
|
mu.Lock()
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("failed to get aggregate data: %w", err)
|
|
} else {
|
|
aggregateData = result
|
|
}
|
|
mu.Unlock()
|
|
}()
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
wg.Wait()
|
|
close(errChan)
|
|
|
|
// Check for errors
|
|
for err := range errChan {
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Data processing failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Build response
|
|
meta := h.calculateMeta(limit, offset, total)
|
|
response := models.` + data.Name + `GetResponse{
|
|
Message: "Data ` + data.NameLower + ` berhasil diambil",
|
|
Data: items,
|
|
Meta: meta,
|
|
}
|
|
|
|
if includeAggregation && aggregateData != nil {
|
|
response.Summary = aggregateData
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// Get` + data.Name + `ByID godoc
|
|
// @Summary Get ` + data.Name + ` by ID
|
|
// @Description Returns a single ` + data.NameLower + ` by ID
|
|
// @Tags ` + data.NameLower + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "` + data.Name + ` ID (UUID)"
|
|
// @Success 200 {object} models.` + data.Name + `GetByIDResponse "Success response"
|
|
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
|
// @Failure 404 {object} models.ErrorResponse "` + data.Name + ` not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/` + singleRoutePath + `/{id} [get]
|
|
func (h *` + data.Name + `Handler) Get` + data.Name + `ByID(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
// Validate UUID format
|
|
if _, err := uuid.Parse(id); err != nil {
|
|
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("satudata")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
item, err := h.get` + data.Name + `ByID(ctx, dbConn, id)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
h.respondError(c, "` + data.Name + ` not found", err, http.StatusNotFound)
|
|
} else {
|
|
h.logAndRespondError(c, "Failed to get ` + data.NameLower + `", err, http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
response := models.` + data.Name + `GetByIDResponse{
|
|
Message: "` + data.Name + ` details retrieved successfully",
|
|
Data: item,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}`
|
|
}
|
|
|
|
func generateCreateMethod(data HandlerData) string {
|
|
var routePath string
|
|
if data.Category != "" {
|
|
routePath = data.Category + "/" + data.NamePlural
|
|
} else {
|
|
routePath = data.NamePlural
|
|
}
|
|
|
|
return `
|
|
|
|
// Create` + data.Name + ` godoc
|
|
// @Summary Create ` + data.NameLower + `
|
|
// @Description Creates a new ` + data.NameLower + ` record
|
|
// @Tags ` + data.NameLower + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body models.` + data.Name + `CreateRequest true "` + data.Name + ` creation request"
|
|
// @Success 201 {object} models.` + data.Name + `CreateResponse "` + data.Name + ` created successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/` + routePath + ` [post]
|
|
func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) {
|
|
var req models.` + data.Name + `CreateRequest
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate request
|
|
if err := validate.Struct(&req); err != nil {
|
|
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("satudata")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
item, err := h.create` + data.Name + `(ctx, dbConn, &req)
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Failed to create ` + data.NameLower + `", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response := models.` + data.Name + `CreateResponse{
|
|
Message: "` + data.Name + ` berhasil dibuat",
|
|
Data: item,
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, response)
|
|
}`
|
|
}
|
|
|
|
func generateUpdateMethod(data HandlerData) string {
|
|
var singleRoutePath string
|
|
if data.Category != "" {
|
|
singleRoutePath = data.Category + "/" + data.NameLower
|
|
} else {
|
|
singleRoutePath = data.NameLower
|
|
}
|
|
|
|
return `
|
|
|
|
// Update` + data.Name + ` godoc
|
|
// @Summary Update ` + data.NameLower + `
|
|
// @Description Updates an existing ` + data.NameLower + ` record
|
|
// @Tags ` + data.NameLower + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "` + data.Name + ` ID (UUID)"
|
|
// @Param request body models.` + data.Name + `UpdateRequest true "` + data.Name + ` update request"
|
|
// @Success 200 {object} models.` + data.Name + `UpdateResponse "` + data.Name + ` updated successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
|
// @Failure 404 {object} models.ErrorResponse "` + data.Name + ` not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/` + singleRoutePath + `/{id} [put]
|
|
func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
// Validate UUID format
|
|
if _, err := uuid.Parse(id); err != nil {
|
|
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req models.` + data.Name + `UpdateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Set ID from path parameter
|
|
req.ID = id
|
|
|
|
// Validate request
|
|
if err := validate.Struct(&req); err != nil {
|
|
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("satudata")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
item, err := h.update` + data.Name + `(ctx, dbConn, &req)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
h.respondError(c, "` + data.Name + ` not found", err, http.StatusNotFound)
|
|
} else {
|
|
h.logAndRespondError(c, "Failed to update ` + data.NameLower + `", err, http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
response := models.` + data.Name + `UpdateResponse{
|
|
Message: "` + data.Name + ` berhasil diperbarui",
|
|
Data: item,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}`
|
|
}
|
|
|
|
func generateDeleteMethod(data HandlerData) string {
|
|
var singleRoutePath string
|
|
if data.Category != "" {
|
|
singleRoutePath = data.Category + "/" + data.NameLower
|
|
} else {
|
|
singleRoutePath = data.NameLower
|
|
}
|
|
|
|
return `
|
|
|
|
// Delete` + data.Name + ` godoc
|
|
// @Summary Delete ` + data.NameLower + `
|
|
// @Description Soft deletes a ` + data.NameLower + ` by setting status to 'deleted'
|
|
// @Tags ` + data.NameLower + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "` + data.Name + ` ID (UUID)"
|
|
// @Success 200 {object} models.` + data.Name + `DeleteResponse "` + data.Name + ` deleted successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
|
// @Failure 404 {object} models.ErrorResponse "` + data.Name + ` not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/` + singleRoutePath + `/{id} [delete]
|
|
func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
// Validate UUID format
|
|
if _, err := uuid.Parse(id); err != nil {
|
|
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("satudata")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
err = h.delete` + data.Name + `(ctx, dbConn, id)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
h.respondError(c, "` + data.Name + ` not found", err, http.StatusNotFound)
|
|
} else {
|
|
h.logAndRespondError(c, "Failed to delete ` + data.NameLower + `", err, http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
response := models.` + data.Name + `DeleteResponse{
|
|
Message: "` + data.Name + ` berhasil dihapus",
|
|
ID: id,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}`
|
|
}
|
|
|
|
func generateStatsMethod(data HandlerData) string {
|
|
var routePath string
|
|
if data.Category != "" {
|
|
routePath = data.Category + "/" + data.NamePlural
|
|
} else {
|
|
routePath = data.NamePlural
|
|
}
|
|
|
|
return `
|
|
|
|
// Get` + data.Name + `Stats godoc
|
|
// @Summary Get ` + data.NameLower + ` statistics
|
|
// @Description Returns comprehensive statistics about ` + data.NameLower + ` data
|
|
// @Tags ` + data.NameLower + `
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param status query string false "Filter statistics by status"
|
|
// @Success 200 {object} models.AggregateData "Statistics data"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/` + routePath + `/stats [get]
|
|
func (h *` + data.Name + `Handler) Get` + data.Name + `Stats(c *gin.Context) {
|
|
dbConn, err := h.db.GetDB("satudata")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
filter := h.parseFilterParams(c)
|
|
aggregateData, err := h.getAggregateData(ctx, dbConn, filter)
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Failed to get statistics", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Statistik ` + data.NameLower + ` berhasil diambil",
|
|
"data": aggregateData,
|
|
})
|
|
}`
|
|
}
|
|
|
|
func generateHelperMethods(data HandlerData) string {
|
|
return `
|
|
|
|
// Database operations
|
|
func (h *` + data.Name + `Handler) get` + data.Name + `ByID(ctx context.Context, dbConn *sql.DB, id string) (*models.` + data.Name + `, error) {
|
|
query := "SELECT id, status, date_created, date_updated, name FROM ` + data.TableName + ` WHERE id = $1 AND status != 'deleted'"
|
|
row := dbConn.QueryRowContext(ctx, query, id)
|
|
|
|
var item models.` + data.Name + `
|
|
err := row.Scan(&item.ID, &item.Status, &item.DateCreated, &item.DateUpdated, &item.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &item, nil
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) create` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *models.` + data.Name + `CreateRequest) (*models.` + data.Name + `, error) {
|
|
id := uuid.New().String()
|
|
now := time.Now()
|
|
|
|
query := "INSERT INTO ` + data.TableName + ` (id, status, date_created, date_updated, name) VALUES ($1, $2, $3, $4, $5) RETURNING id, status, date_created, date_updated, name"
|
|
row := dbConn.QueryRowContext(ctx, query, id, req.Status, now, now, req.Name)
|
|
|
|
var item models.` + data.Name + `
|
|
err := row.Scan(&item.ID, &item.Status, &item.DateCreated, &item.DateUpdated, &item.Name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create ` + data.NameLower + `: %w", err)
|
|
}
|
|
|
|
return &item, nil
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) update` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *models.` + data.Name + `UpdateRequest) (*models.` + data.Name + `, error) {
|
|
now := time.Now()
|
|
|
|
query := "UPDATE ` + data.TableName + ` SET status = $2, date_updated = $3, name = $4 WHERE id = $1 AND status != 'deleted' RETURNING id, status, date_created, date_updated, name"
|
|
row := dbConn.QueryRowContext(ctx, query, req.ID, req.Status, now, req.Name)
|
|
|
|
var item models.` + data.Name + `
|
|
err := row.Scan(&item.ID, &item.Status, &item.DateCreated, &item.DateUpdated, &item.Name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to update ` + data.NameLower + `: %w", err)
|
|
}
|
|
|
|
return &item, nil
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) delete` + data.Name + `(ctx context.Context, dbConn *sql.DB, id string) error {
|
|
now := time.Now()
|
|
|
|
query := "UPDATE ` + data.TableName + ` SET status = 'deleted', date_updated = $2 WHERE id = $1 AND status != 'deleted'"
|
|
result, err := dbConn.ExecContext(ctx, query, id, now)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete ` + data.NameLower + `: %w", err)
|
|
}
|
|
|
|
rowsAffected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get affected rows: %w", err)
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
|
return sql.ErrNoRows
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) fetch` + data.Name + `s(ctx context.Context, dbConn *sql.DB, filter models.` + data.Name + `Filter, limit, offset int) ([]models.` + data.Name + `, error) {
|
|
whereClause, args := h.buildWhereClause(filter)
|
|
query := fmt.Sprintf("SELECT id, status, date_created, date_updated, name FROM ` + data.TableName + ` WHERE %s ORDER BY date_created DESC NULLS LAST LIMIT $%d OFFSET $%d", whereClause, len(args)+1, len(args)+2)
|
|
args = append(args, limit, offset)
|
|
|
|
rows, err := dbConn.QueryContext(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("fetch ` + data.NamePlural + ` query failed: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := make([]models.` + data.Name + `, 0, limit)
|
|
for rows.Next() {
|
|
var item models.` + data.Name + `
|
|
err := rows.Scan(&item.ID, &item.Status, &item.DateCreated, &item.DateUpdated, &item.Name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scan ` + data.Name + ` failed: %w", err)
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("rows iteration error: %w", err)
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter models.` + data.Name + `Filter, total *int) error {
|
|
whereClause, args := h.buildWhereClause(filter)
|
|
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM ` + data.TableName + ` WHERE %s", whereClause)
|
|
|
|
if err := dbConn.QueryRowContext(ctx, countQuery, args...).Scan(total); err != nil {
|
|
return fmt.Errorf("total count query failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter models.` + data.Name + `Filter) (*models.AggregateData, error) {
|
|
aggregate := &models.AggregateData{
|
|
ByStatus: make(map[string]int),
|
|
}
|
|
|
|
whereClause, args := h.buildWhereClause(filter)
|
|
statusQuery := fmt.Sprintf("SELECT status, COUNT(*) FROM ` + data.TableName + ` WHERE %s GROUP BY status ORDER BY status", whereClause)
|
|
|
|
rows, err := dbConn.QueryContext(ctx, statusQuery, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("status query failed: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var status string
|
|
var count int
|
|
if err := rows.Scan(&status, &count); err != nil {
|
|
return nil, fmt.Errorf("status scan failed: %w", err)
|
|
}
|
|
|
|
aggregate.ByStatus[status] = count
|
|
switch status {
|
|
case "active":
|
|
aggregate.TotalActive = count
|
|
case "draft":
|
|
aggregate.TotalDraft = count
|
|
case "inactive":
|
|
aggregate.TotalInactive = count
|
|
}
|
|
}
|
|
|
|
return aggregate, nil
|
|
}
|
|
|
|
// Helper methods
|
|
func (h *` + data.Name + `Handler) logAndRespondError(c *gin.Context, message string, err error, statusCode int) {
|
|
log.Printf("[ERROR] %s: %v", message, err)
|
|
h.respondError(c, message, err, statusCode)
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) respondError(c *gin.Context, message string, err error, statusCode int) {
|
|
errorMessage := message
|
|
if gin.Mode() == gin.ReleaseMode {
|
|
errorMessage = "Internal server error"
|
|
}
|
|
|
|
c.JSON(statusCode, models.ErrorResponse{
|
|
Error: errorMessage,
|
|
Code: statusCode,
|
|
Message: err.Error(),
|
|
Timestamp: time.Now(),
|
|
})
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) parsePaginationParams(c *gin.Context) (int, int, error) {
|
|
limit := 10 // Default limit
|
|
offset := 0 // Default offset
|
|
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
parsedLimit, err := strconv.Atoi(limitStr)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("invalid limit parameter: %s", limitStr)
|
|
}
|
|
|
|
if parsedLimit <= 0 {
|
|
return 0, 0, fmt.Errorf("limit must be greater than 0")
|
|
}
|
|
|
|
if parsedLimit > 100 {
|
|
return 0, 0, fmt.Errorf("limit cannot exceed 100")
|
|
}
|
|
|
|
limit = parsedLimit
|
|
}
|
|
|
|
if offsetStr := c.Query("offset"); offsetStr != "" {
|
|
parsedOffset, err := strconv.Atoi(offsetStr)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("invalid offset parameter: %s", offsetStr)
|
|
}
|
|
|
|
if parsedOffset < 0 {
|
|
return 0, 0, fmt.Errorf("offset cannot be negative")
|
|
}
|
|
|
|
offset = parsedOffset
|
|
}
|
|
|
|
return limit, offset, nil
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) parseFilterParams(c *gin.Context) models.` + data.Name + `Filter {
|
|
filter := models.` + data.Name + `Filter{}
|
|
|
|
if status := c.Query("status"); status != "" {
|
|
if models.IsValidStatus(status) {
|
|
filter.Status = &status
|
|
}
|
|
}
|
|
|
|
if search := c.Query("search"); search != "" {
|
|
filter.Search = &search
|
|
}
|
|
|
|
// Parse date filters
|
|
if dateFromStr := c.Query("date_from"); dateFromStr != "" {
|
|
if dateFrom, err := time.Parse("2006-01-02", dateFromStr); err == nil {
|
|
filter.DateFrom = &dateFrom
|
|
}
|
|
}
|
|
|
|
if dateToStr := c.Query("date_to"); dateToStr != "" {
|
|
if dateTo, err := time.Parse("2006-01-02", dateToStr); err == nil {
|
|
filter.DateTo = &dateTo
|
|
}
|
|
}
|
|
|
|
return filter
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) buildWhereClause(filter models.` + data.Name + `Filter) (string, []interface{}) {
|
|
conditions := []string{"status != 'deleted'"}
|
|
args := []interface{}{}
|
|
paramCount := 1
|
|
|
|
if filter.Status != nil {
|
|
conditions = append(conditions, fmt.Sprintf("status = $%d", paramCount))
|
|
args = append(args, *filter.Status)
|
|
paramCount++
|
|
}
|
|
|
|
if filter.Search != nil {
|
|
searchCondition := fmt.Sprintf("name ILIKE $%d", paramCount)
|
|
conditions = append(conditions, searchCondition)
|
|
searchTerm := "%" + *filter.Search + "%"
|
|
args = append(args, searchTerm)
|
|
paramCount++
|
|
}
|
|
|
|
if filter.DateFrom != nil {
|
|
conditions = append(conditions, fmt.Sprintf("date_created >= $%d", paramCount))
|
|
args = append(args, *filter.DateFrom)
|
|
paramCount++
|
|
}
|
|
|
|
if filter.DateTo != nil {
|
|
conditions = append(conditions, fmt.Sprintf("date_created <= $%d", paramCount))
|
|
args = append(args, filter.DateTo.Add(24*time.Hour-time.Nanosecond))
|
|
paramCount++
|
|
}
|
|
|
|
return strings.Join(conditions, " AND "), args
|
|
}
|
|
|
|
func (h *` + data.Name + `Handler) calculateMeta(limit, offset, total int) models.MetaResponse {
|
|
totalPages := 0
|
|
currentPage := 1
|
|
|
|
if limit > 0 {
|
|
totalPages = (total + limit - 1) / limit // Ceiling division
|
|
currentPage = (offset / limit) + 1
|
|
}
|
|
|
|
return models.MetaResponse{
|
|
Limit: limit,
|
|
Offset: offset,
|
|
Total: total,
|
|
TotalPages: totalPages,
|
|
CurrentPage: currentPage,
|
|
HasNext: offset+limit < total,
|
|
HasPrev: offset > 0,
|
|
}
|
|
}`
|
|
}
|
|
|
|
// ================= MODEL GENERATION =====================
|
|
func generateModelFile(data HandlerData, modelDir string) {
|
|
modelContent := `package models
|
|
|
|
import (
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
// NullableInt32 is a custom type to replace sql.NullInt32 for swagger compatibility
|
|
type NullableInt32 struct {
|
|
Int32 int32 ` + "`json:\"int32,omitempty\"`" + `
|
|
Valid bool ` + "`json:\"valid\"`" + `
|
|
}
|
|
|
|
// Scan implements the sql.Scanner interface for NullableInt32
|
|
func (n *NullableInt32) Scan(value interface{}) error {
|
|
var ni sql.NullInt32
|
|
if err := ni.Scan(value); err != nil {
|
|
return err
|
|
}
|
|
|
|
n.Int32 = ni.Int32
|
|
n.Valid = ni.Valid
|
|
return nil
|
|
}
|
|
|
|
// Value implements the driver.Valuer interface for NullableInt32
|
|
func (n NullableInt32) Value() (driver.Value, error) {
|
|
if !n.Valid {
|
|
return nil, nil
|
|
}
|
|
|
|
return n.Int32, nil
|
|
}
|
|
|
|
// ` + data.Name + ` represents the data structure for the ` + data.NameLower + ` table
|
|
type ` + data.Name + ` struct {
|
|
ID string ` + "`json:\"id\" db:\"id\"`" + `
|
|
Status string ` + "`json:\"status\" db:\"status\"`" + `
|
|
Sort NullableInt32 ` + "`json:\"sort,omitempty\" db:\"sort\"`" + `
|
|
UserCreated sql.NullString ` + "`json:\"user_created,omitempty\" db:\"user_created\"`" + `
|
|
DateCreated sql.NullTime ` + "`json:\"date_created,omitempty\" db:\"date_created\"`" + `
|
|
UserUpdated sql.NullString ` + "`json:\"user_updated,omitempty\" db:\"user_updated\"`" + `
|
|
DateUpdated sql.NullTime ` + "`json:\"date_updated,omitempty\" db:\"date_updated\"`" + `
|
|
Name sql.NullString ` + "`json:\"name,omitempty\" db:\"name\"`" + `
|
|
}
|
|
|
|
// Custom JSON marshaling for ` + data.Name + `
|
|
func (r ` + data.Name + `) MarshalJSON() ([]byte, error) {
|
|
type Alias ` + data.Name + `
|
|
aux := &struct {
|
|
Sort *int ` + "`json:\"sort,omitempty\"`" + `
|
|
UserCreated *string ` + "`json:\"user_created,omitempty\"`" + `
|
|
DateCreated *time.Time ` + "`json:\"date_created,omitempty\"`" + `
|
|
UserUpdated *string ` + "`json:\"user_updated,omitempty\"`" + `
|
|
DateUpdated *time.Time ` + "`json:\"date_updated,omitempty\"`" + `
|
|
Name *string ` + "`json:\"name,omitempty\"`" + `
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(&r),
|
|
}
|
|
|
|
if r.Sort.Valid {
|
|
sort := int(r.Sort.Int32)
|
|
aux.Sort = &sort
|
|
}
|
|
|
|
if r.UserCreated.Valid {
|
|
aux.UserCreated = &r.UserCreated.String
|
|
}
|
|
|
|
if r.DateCreated.Valid {
|
|
aux.DateCreated = &r.DateCreated.Time
|
|
}
|
|
|
|
if r.UserUpdated.Valid {
|
|
aux.UserUpdated = &r.UserUpdated.String
|
|
}
|
|
|
|
if r.DateUpdated.Valid {
|
|
aux.DateUpdated = &r.DateUpdated.Time
|
|
}
|
|
|
|
if r.Name.Valid {
|
|
aux.Name = &r.Name.String
|
|
}
|
|
|
|
return json.Marshal(aux)
|
|
}
|
|
|
|
// Helper methods
|
|
func (r *` + data.Name + `) GetName() string {
|
|
if r.Name.Valid {
|
|
return r.Name.String
|
|
}
|
|
return ""
|
|
}`
|
|
|
|
// Add request/response structs based on enabled methods
|
|
if data.HasGet {
|
|
modelContent += `
|
|
|
|
// Response struct for GET by ID
|
|
type ` + data.Name + `GetByIDResponse struct {
|
|
Message string ` + "`json:\"message\"`" + `
|
|
Data *` + data.Name + ` ` + "`json:\"data\"`" + `
|
|
}
|
|
|
|
// Enhanced GET response with pagination and aggregation
|
|
type ` + data.Name + `GetResponse struct {
|
|
Message string ` + "`json:\"message\"`" + `
|
|
Data []` + data.Name + ` ` + "`json:\"data\"`" + `
|
|
Meta MetaResponse ` + "`json:\"meta\"`" + `
|
|
Summary *AggregateData ` + "`json:\"summary,omitempty\"`" + `
|
|
}`
|
|
}
|
|
|
|
if data.HasPost {
|
|
modelContent += `
|
|
|
|
// Request struct for create
|
|
type ` + data.Name + `CreateRequest struct {
|
|
Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + `
|
|
Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + `
|
|
}
|
|
|
|
// Response struct for create
|
|
type ` + data.Name + `CreateResponse struct {
|
|
Message string ` + "`json:\"message\"`" + `
|
|
Data *` + data.Name + ` ` + "`json:\"data\"`" + `
|
|
}`
|
|
}
|
|
|
|
if data.HasPut {
|
|
modelContent += `
|
|
|
|
// Update request
|
|
type ` + data.Name + `UpdateRequest struct {
|
|
ID string ` + "`json:\"-\" validate:\"required,uuid4\"`" + ` // ID dari URL path
|
|
Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + `
|
|
Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + `
|
|
}
|
|
|
|
// Response struct for update
|
|
type ` + data.Name + `UpdateResponse struct {
|
|
Message string ` + "`json:\"message\"`" + `
|
|
Data *` + data.Name + ` ` + "`json:\"data\"`" + `
|
|
}`
|
|
}
|
|
|
|
if data.HasDelete {
|
|
modelContent += `
|
|
|
|
// Response struct for delete
|
|
type ` + data.Name + `DeleteResponse struct {
|
|
Message string ` + "`json:\"message\"`" + `
|
|
ID string ` + "`json:\"id\"`" + `
|
|
}`
|
|
}
|
|
|
|
// Add common structs
|
|
modelContent += `
|
|
|
|
// Metadata for pagination
|
|
type MetaResponse struct {
|
|
Limit int ` + "`json:\"limit\"`" + `
|
|
Offset int ` + "`json:\"offset\"`" + `
|
|
Total int ` + "`json:\"total\"`" + `
|
|
TotalPages int ` + "`json:\"total_pages\"`" + `
|
|
CurrentPage int ` + "`json:\"current_page\"`" + `
|
|
HasNext bool ` + "`json:\"has_next\"`" + `
|
|
HasPrev bool ` + "`json:\"has_prev\"`" + `
|
|
}
|
|
|
|
// Aggregate data for summary
|
|
type AggregateData struct {
|
|
TotalActive int ` + "`json:\"total_active\"`" + `
|
|
TotalDraft int ` + "`json:\"total_draft\"`" + `
|
|
TotalInactive int ` + "`json:\"total_inactive\"`" + `
|
|
ByStatus map[string]int ` + "`json:\"by_status\"`" + `
|
|
LastUpdated *time.Time ` + "`json:\"last_updated,omitempty\"`" + `
|
|
CreatedToday int ` + "`json:\"created_today\"`" + `
|
|
UpdatedToday int ` + "`json:\"updated_today\"`" + `
|
|
}
|
|
|
|
// Error response
|
|
type ErrorResponse struct {
|
|
Error string ` + "`json:\"error\"`" + `
|
|
Code int ` + "`json:\"code\"`" + `
|
|
Message string ` + "`json:\"message\"`" + `
|
|
Timestamp time.Time ` + "`json:\"timestamp\"`" + `
|
|
}
|
|
|
|
// Filter struct for query parameters
|
|
type ` + data.Name + `Filter struct {
|
|
Status *string ` + "`json:\"status,omitempty\" form:\"status\"`" + `
|
|
Search *string ` + "`json:\"search,omitempty\" form:\"search\"`" + `
|
|
DateFrom *time.Time ` + "`json:\"date_from,omitempty\" form:\"date_from\"`" + `
|
|
DateTo *time.Time ` + "`json:\"date_to,omitempty\" form:\"date_to\"`" + `
|
|
}
|
|
|
|
// Validation constants
|
|
const (
|
|
StatusDraft = "draft"
|
|
StatusActive = "active"
|
|
StatusInactive = "inactive"
|
|
StatusDeleted = "deleted"
|
|
)
|
|
|
|
// ValidStatuses for validation
|
|
var ValidStatuses = []string{StatusDraft, StatusActive, StatusInactive}
|
|
|
|
// IsValidStatus helper function
|
|
func IsValidStatus(status string) bool {
|
|
for _, validStatus := range ValidStatuses {
|
|
if status == validStatus {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}`
|
|
|
|
writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent)
|
|
}
|
|
|
|
// ================= ROUTES GENERATION =====================
|
|
func updateRoutesFile(data HandlerData) {
|
|
routesFile := "internal/routes/v1/routes.go"
|
|
content, err := os.ReadFile(routesFile)
|
|
if err != nil {
|
|
fmt.Printf("⚠️ Could not read routes.go: %v\n", err)
|
|
fmt.Printf("📝 Please manually add these routes to your routes.go file:\n")
|
|
printRoutesSample(data)
|
|
return
|
|
}
|
|
|
|
routesContent := string(content)
|
|
|
|
// Build import path - PERBAIKAN UTAMA
|
|
var importPath, importAlias string
|
|
if data.Category != "" {
|
|
importPath = fmt.Sprintf("%s/internal/handlers/%s", data.ModuleName, data.Category)
|
|
importAlias = data.NameLower + "Handlers"
|
|
} else {
|
|
importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName)
|
|
importAlias = data.NameLower + "Handlers"
|
|
}
|
|
|
|
// import
|
|
importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath)
|
|
if !strings.Contains(routesContent, importPattern) {
|
|
importToAdd := fmt.Sprintf("\t%s \"%s\"", importAlias, importPath)
|
|
if strings.Contains(routesContent, "import (") {
|
|
routesContent = strings.Replace(routesContent, "import (",
|
|
"import (\n"+importToAdd, 1)
|
|
}
|
|
}
|
|
|
|
// Build route paths
|
|
var routesPath, singleRoutePath string
|
|
if data.Category != "" {
|
|
routesPath = data.Category + "/" + data.NamePlural
|
|
singleRoutePath = data.Category + "/" + data.NameLower
|
|
} else {
|
|
routesPath = data.NamePlural
|
|
singleRoutePath = data.NameLower
|
|
}
|
|
|
|
// routes
|
|
newRoutes := fmt.Sprintf("\t\t// %s endpoints\n", data.Name)
|
|
newRoutes += fmt.Sprintf("\t\t%sHandler := %s.New%sHandler()\n",
|
|
data.NameLower, importAlias, data.Name)
|
|
|
|
if data.HasGet {
|
|
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s\", %sHandler.Get%s)\n",
|
|
routesPath, data.NameLower, data.Name)
|
|
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%sByID)\n",
|
|
singleRoutePath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasPost {
|
|
newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n",
|
|
routesPath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasPut {
|
|
newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s/:id\", %sHandler.Update%s)\n",
|
|
singleRoutePath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasDelete {
|
|
newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n",
|
|
singleRoutePath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasStats {
|
|
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/stats\", %sHandler.Get%sStats)\n",
|
|
routesPath, data.NameLower, data.Name)
|
|
}
|
|
|
|
newRoutes += "\n"
|
|
|
|
insertMarker := "\t\tprotected := v1.Group(\"/\")"
|
|
if strings.Contains(routesContent, insertMarker) {
|
|
if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) {
|
|
routesContent = strings.Replace(routesContent, insertMarker,
|
|
newRoutes+insertMarker, 1)
|
|
} else {
|
|
fmt.Printf("✅ Routes for %s already exist, skipping...\n", data.Name)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil {
|
|
fmt.Printf("Error writing routes.go: %v\n", err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("✅ Updated routes.go with %s endpoints\n", data.Name)
|
|
}
|
|
|
|
func printRoutesSample(data HandlerData) {
|
|
var routesPath, singleRoutePath string
|
|
if data.Category != "" {
|
|
routesPath = data.Category + "/" + data.NamePlural
|
|
singleRoutePath = data.Category + "/" + data.NameLower
|
|
} else {
|
|
routesPath = data.NamePlural
|
|
singleRoutePath = data.NameLower
|
|
}
|
|
|
|
var importAlias string
|
|
if data.Category != "" {
|
|
importAlias = data.NameLower + "Handlers"
|
|
} else {
|
|
importAlias = data.NameLower + "Handlers"
|
|
}
|
|
|
|
fmt.Printf(`
|
|
// %s endpoints
|
|
%sHandler := %s.New%sHandler()
|
|
`, data.Name, data.NameLower, importAlias, data.Name)
|
|
|
|
if data.HasGet {
|
|
fmt.Printf("\tv1.GET(\"/%s\", %sHandler.Get%s)\n", routesPath, data.NameLower, data.Name)
|
|
fmt.Printf("\tv1.GET(\"/%s/:id\", %sHandler.Get%sByID)\n", singleRoutePath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasPost {
|
|
fmt.Printf("\tv1.POST(\"/%s\", %sHandler.Create%s)\n", routesPath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasPut {
|
|
fmt.Printf("\tv1.PUT(\"/%s/:id\", %sHandler.Update%s)\n", singleRoutePath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasDelete {
|
|
fmt.Printf("\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", singleRoutePath, data.NameLower, data.Name)
|
|
}
|
|
|
|
if data.HasStats {
|
|
fmt.Printf("\tv1.GET(\"/%s/stats\", %sHandler.Get%sStats)\n", routesPath, data.NameLower, data.Name)
|
|
}
|
|
|
|
fmt.Println()
|
|
}
|
|
|
|
// ================= UTILITY FUNCTIONS =====================
|
|
func writeFile(filename, content string) {
|
|
if err := os.WriteFile(filename, []byte(content), 0644); err != nil {
|
|
fmt.Printf("❌ Error creating file %s: %v\n", filename, err)
|
|
return
|
|
}
|
|
fmt.Printf("✅ Generated: %s\n", filename)
|
|
}
|