Creat Service BPJS

This commit is contained in:
2025-08-18 20:02:34 +07:00
parent 1c4f65ffd8
commit 4d72e31352
6 changed files with 988 additions and 170 deletions

View File

@@ -13,6 +13,8 @@ type HandlerData struct {
Name string
NameLower string
NamePlural string
Category string
CategoryPath string
ModuleName string
TableName string
HasGet bool
@@ -27,12 +29,16 @@ type HandlerData struct {
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run generate-handler.go entity [methods]")
fmt.Println("Example: go run generate-handler.go order get post put delete stats")
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)
}
entityName := strings.Title(os.Args[1]) // PascalCase entity name
// Parse entity path (could be "entity" or "category/entity")
entityPath := os.Args[1]
methods := []string{}
if len(os.Args) > 2 {
methods = os.Args[2:]
@@ -41,14 +47,40 @@ func main() {
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"
tableName := "data_" + entityLower
// 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,
@@ -77,10 +109,20 @@ func main() {
data.HasStats = true
}
// Create directories
handlerDir := filepath.Join("internal", "handlers", entityLower)
modelDir := filepath.Join("internal", "models", entityLower)
// 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)
@@ -93,20 +135,29 @@ func main() {
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) {
// FIXED: Proper string formatting
// 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 "` + data.ModuleName + `/internal/models/` + data.NameLower + `"
models "` + modelsImportPath + `"
"context"
"database/sql"
"fmt"
@@ -123,8 +174,8 @@ import (
)
var (
db database.Service
once sync.Once
db database.Service
once sync.Once
validate *validator.Validate
)
@@ -156,35 +207,47 @@ 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 {
// FIXED: Proper formatting without printf placeholders
// 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
@@ -199,7 +262,7 @@ func generateGetMethods(data HandlerData) string {
// @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/` + data.NamePlural + ` [get]
// @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)
@@ -225,12 +288,12 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) {
// Execute concurrent operations
var (
items []models.` + data.Name + `
total int
items []models.` + data.Name + `
total int
aggregateData *models.AggregateData
wg sync.WaitGroup
errChan = make(chan error, 3)
mu sync.Mutex
wg sync.WaitGroup
errChan = make(chan error, 3)
mu sync.Mutex
)
// Fetch total count
@@ -244,7 +307,7 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) {
}
}()
// Fetch main data - FIXED: Proper method name
// Fetch main data
wg.Add(1)
go func() {
defer wg.Done()
@@ -290,8 +353,8 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) {
meta := h.calculateMeta(limit, offset, total)
response := models.` + data.Name + `GetResponse{
Message: "Data ` + data.NameLower + ` berhasil diambil",
Data: items,
Meta: meta,
Data: items,
Meta: meta,
}
if includeAggregation && aggregateData != nil {
@@ -312,7 +375,7 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) {
// @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/` + data.NameLower + `/{id} [get]
// @Router /api/v1/` + singleRoutePath + `/{id} [get]
func (h *` + data.Name + `Handler) Get` + data.Name + `ByID(c *gin.Context) {
id := c.Param("id")
@@ -343,16 +406,23 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `ByID(c *gin.Context) {
response := models.` + data.Name + `GetByIDResponse{
Message: "` + data.Name + ` details retrieved successfully",
Data: item,
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
@@ -363,9 +433,10 @@ func generateCreateMethod(data HandlerData) string {
// @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/` + data.NamePlural + ` [post]
// @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
@@ -394,16 +465,23 @@ func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) {
response := models.` + data.Name + `CreateResponse{
Message: "` + data.Name + ` berhasil dibuat",
Data: item,
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
@@ -416,7 +494,7 @@ func generateUpdateMethod(data HandlerData) string {
// @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/` + data.NameLower + `/{id} [put]
// @Router /api/v1/` + singleRoutePath + `/{id} [put]
func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) {
id := c.Param("id")
@@ -462,16 +540,23 @@ func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) {
response := models.` + data.Name + `UpdateResponse{
Message: "` + data.Name + ` berhasil diperbarui",
Data: item,
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'
@@ -483,7 +568,7 @@ func generateDeleteMethod(data HandlerData) string {
// @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/` + data.NameLower + `/{id} [delete]
// @Router /api/v1/` + singleRoutePath + `/{id} [delete]
func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) {
id := c.Param("id")
@@ -514,16 +599,23 @@ func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) {
response := models.` + data.Name + `DeleteResponse{
Message: "` + data.Name + ` berhasil dihapus",
ID: id,
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
@@ -533,7 +625,7 @@ func generateStatsMethod(data HandlerData) string {
// @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/` + data.NamePlural + `/stats [get]
// @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 {
@@ -553,15 +645,14 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `Stats(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Statistik ` + data.NameLower + ` berhasil diambil",
"data": aggregateData,
"data": aggregateData,
})
}
`
}`
}
func generateHelperMethods(data HandlerData) string {
// FIXED: All method names and types properly formatted
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'"
@@ -609,6 +700,7 @@ func (h *` + data.Name + `Handler) update` + data.Name + `(ctx context.Context,
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 {
@@ -630,8 +722,8 @@ func (h *` + data.Name + `Handler) delete` + data.Name + `(ctx context.Context,
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)
@@ -658,9 +750,11 @@ func (h *` + data.Name + `Handler) fetch` + data.Name + `s(ctx context.Context,
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
}
@@ -671,7 +765,7 @@ func (h *` + data.Name + `Handler) getAggregateData(ctx context.Context, dbConn
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)
@@ -684,8 +778,8 @@ func (h *` + data.Name + `Handler) getAggregateData(ctx context.Context, dbConn
if err := rows.Scan(&status, &count); err != nil {
return nil, fmt.Errorf("status scan failed: %w", err)
}
aggregate.ByStatus[status] = count
aggregate.ByStatus[status] = count
switch status {
case "active":
aggregate.TotalActive = count
@@ -712,9 +806,9 @@ func (h *` + data.Name + `Handler) respondError(c *gin.Context, message string,
}
c.JSON(statusCode, models.ErrorResponse{
Error: errorMessage,
Code: statusCode,
Message: err.Error(),
Error: errorMessage,
Code: statusCode,
Message: err.Error(),
Timestamp: time.Now(),
})
}
@@ -728,12 +822,15 @@ func (h *` + data.Name + `Handler) parsePaginationParams(c *gin.Context) (int, i
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
}
@@ -742,9 +839,11 @@ func (h *` + data.Name + `Handler) parsePaginationParams(c *gin.Context) (int, i
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
}
@@ -817,26 +916,25 @@ func (h *` + data.Name + `Handler) buildWhereClause(filter models.` + data.Name
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,
Limit: limit,
Offset: offset,
Total: total,
TotalPages: totalPages,
CurrentPage: currentPage,
HasNext: offset+limit < total,
HasPrev: offset > 0,
HasNext: offset+limit < total,
HasPrev: offset > 0,
}
}
`
}`
}
// ================= MODEL GENERATION =====================
func generateModelFile(data HandlerData, modelDir string) {
modelContent := `package models
@@ -850,7 +948,7 @@ import (
// NullableInt32 is a custom type to replace sql.NullInt32 for swagger compatibility
type NullableInt32 struct {
Int32 int32 ` + "`json:\"int32,omitempty\"`" + `
Valid bool ` + "`json:\"valid\"`" + `
Valid bool ` + "`json:\"valid\"`" + `
}
// Scan implements the sql.Scanner interface for NullableInt32
@@ -859,6 +957,7 @@ func (n *NullableInt32) Scan(value interface{}) error {
if err := ni.Scan(value); err != nil {
return err
}
n.Int32 = ni.Int32
n.Valid = ni.Valid
return nil
@@ -869,31 +968,32 @@ 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\"`" + `
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\"`" + `
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\"`" + `
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\"`" + `
Sort *int ` + "`json:\"sort,omitempty\"`" + `
UserCreated *string ` + "`json:\"user_created,omitempty\"`" + `
DateCreated *time.Time ` + "`json:\"date_created,omitempty\"`" + `
UserUpdated *string ` + "`json:\"user_updated,omitempty\"`" + `
UserUpdated *string ` + "`json:\"user_updated,omitempty\"`" + `
DateUpdated *time.Time ` + "`json:\"date_updated,omitempty\"`" + `
Name *string ` + "`json:\"name,omitempty\"`" + `
Name *string ` + "`json:\"name,omitempty\"`" + `
*Alias
}{
Alias: (*Alias)(&r),
@@ -903,18 +1003,23 @@ func (r ` + data.Name + `) MarshalJSON() ([]byte, error) {
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
}
@@ -928,117 +1033,117 @@ func (r *` + data.Name + `) GetName() string {
return r.Name.String
}
return ""
}
`
}`
// Add request/response structs
// 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\"`" + `
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\"`" + `
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\"`" + `
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\"`" + `
}
`
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\"`" + `
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\"`" + `
}
`
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\"`" + `
}
`
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\"`" + `
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\"`" + `
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\"`" + `
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\"`" + `
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\"`" + `
DateTo *time.Time ` + "`json:\"date_to,omitempty\" form:\"date_to\"`" + `
}
// Validation constants
const (
StatusDraft = "draft"
StatusActive = "active"
StatusDraft = "draft"
StatusActive = "active"
StatusInactive = "inactive"
StatusDeleted = "deleted"
StatusDeleted = "deleted"
)
// ValidStatuses for validation
@@ -1052,41 +1157,13 @@ func IsValidStatus(status string) bool {
}
}
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)
// // Add import
// importPattern := data.NameLower + `Handlers "` + data.ModuleName + `/internal/handlers/` + data.NameLower + `"`
// if !strings.Contains(routesContent, importPattern) {
// fmt.Printf("⚠️ Please add this import to your routes.go file:\n")
// fmt.Printf("import %sHandlers \"%s/internal/handlers/%s\"\n\n", data.NameLower, data.ModuleName, data.NameLower)
// }
// // Check if routes already exist
// if strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) {
// fmt.Printf("⚠️ Routes for %s already exist, skipping...\n", data.Name)
// return
// }
// fmt.Printf("📝 Please manually add these routes to your routes.go file:\n")
// printRoutesSample(data)
routesFile := "internal/routes/v1/routes.go"
content, err := os.ReadFile(routesFile)
if err != nil {
@@ -1095,43 +1172,71 @@ func updateRoutesFile(data HandlerData) {
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("%sHandlers \"%s/internal/handlers/%s\"",
data.NameLower, data.ModuleName, data.NameLower)
importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath)
if !strings.Contains(routesContent, importPattern) {
importToAdd := fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"",
data.NameLower, data.ModuleName, data.NameLower)
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 := %sHandlers.New%sHandler()\n",
data.NameLower, data.NameLower, 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",
data.NamePlural, data.NameLower, data.Name)
routesPath, data.NameLower, data.Name)
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%sByID)\n",
data.NameLower, data.NameLower, data.Name)
singleRoutePath, data.NameLower, data.Name)
}
if data.HasPost {
newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n",
data.NamePlural, data.NameLower, data.Name)
routesPath, data.NameLower, data.Name)
}
if data.HasPut {
newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s/:id\", %sHandler.Update%s)\n",
data.NameLower, data.NameLower, data.Name)
singleRoutePath, data.NameLower, data.Name)
}
if data.HasDelete {
newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n",
data.NameLower, data.NameLower, data.Name)
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(\"/\")"
@@ -1141,6 +1246,7 @@ func updateRoutesFile(data HandlerData) {
newRoutes+insertMarker, 1)
} else {
fmt.Printf("✅ Routes for %s already exist, skipping...\n", data.Name)
return
}
}
@@ -1148,36 +1254,57 @@ func updateRoutesFile(data HandlerData) {
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 := %sHandlers.New%sHandler()
`, data.Name, data.NameLower, data.NameLower, data.Name)
// %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", data.NamePlural, data.NameLower, data.Name)
fmt.Printf("\tv1.GET(\"/%s/:id\", %sHandler.Get%sByID)\n", data.NameLower, data.NameLower, data.Name)
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", data.NamePlural, data.NameLower, data.Name)
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", data.NameLower, data.NameLower, data.Name)
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", data.NameLower, data.NameLower, data.Name)
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", data.NamePlural, data.NameLower, data.Name)
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)