Perbaikan Generate code

This commit is contained in:
meninjar
2025-11-03 05:56:41 +00:00
parent 19324041b8
commit db5a19e9cc
12 changed files with 7018 additions and 1826 deletions

View File

@@ -340,11 +340,6 @@ BPJS_SECRETKEY=1bV36ASDQQ3512D
**Generate Handler untuk Retribusi:**
```bash
# Generate handler dasar
go run tools/general/generate-handler.go retribusi get post put delete
# Generate dengan fitur advanced
go run tools/general/generate-handler.go retribusi get post put delete dynamic search stats
# Config
go run tools/general/generate-handler.go --config tools/general/services-config.yaml --verbose
@@ -352,7 +347,7 @@ go run tools/general/generate-handler.go --config tools/general/services-config.
```
***
OBANESTHESI0003
## 🚀 Deployment
### 🐳 Docker Deployment

View File

@@ -394,22 +394,22 @@ func loadKeycloakConfig() KeycloakConfig {
func (c *Config) loadDatabaseConfigs() {
// Load PostgreSQL configurations
c.addPostgreSQLConfigs()
// c.addPostgreSQLConfigs()
// Load MySQL configurations
c.addMySQLConfigs()
// // Load MySQL configurations
// c.addMySQLConfigs()
// Load MongoDB configurations
c.addMongoDBConfigs()
// // Load MongoDB configurations
// c.addMongoDBConfigs()
// Load SQLite configurations
c.addSQLiteConfigs()
// // Load SQLite configurations
// c.addSQLiteConfigs()
// Load custom database configurations from environment variables
c.loadCustomDatabaseConfigs()
// Remove duplicate database configurations
c.removeDuplicateDatabases()
// c.removeDuplicateDatabases()
}
func (c *Config) removeDuplicateDatabases() {

View File

@@ -127,16 +127,16 @@ func (s *service) addDatabase(name string, config config.DatabaseConfig) error {
defer s.mu.Unlock()
// Check for duplicate database connections
for existingName, existingConfig := range s.configs {
if existingName != name &&
existingConfig.Host == config.Host &&
existingConfig.Port == config.Port &&
existingConfig.Database == config.Database &&
existingConfig.Type == config.Type {
log.Printf("⚠️ Database %s appears to be a duplicate of %s (same host:port:database), skipping connection", name, existingName)
return nil
}
}
// for existingName, existingConfig := range s.configs {
// if existingName != name &&
// existingConfig.Host == config.Host &&
// existingConfig.Port == config.Port &&
// existingConfig.Database == config.Database &&
// existingConfig.Type == config.Type {
// log.Printf("⚠️ Database %s appears to be a duplicate of %s (same host:port:database), skipping connection", name, existingName)
// return nil
// }
// }
var db *sql.DB
var err error

View File

File diff suppressed because it is too large Load Diff

View File

@@ -172,6 +172,7 @@ func NewRetribusiHandler() *RetribusiHandler {
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis [get]
func (h *RetribusiHandler) GetRetribusi(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
// CHANGE: Increase timeout for complex queries
ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second)
defer cancel()
@@ -179,9 +180,31 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) {
// CHANGE: Use the core fetchRetribusisDynamic function for all data retrieval logic.
// We only need to build DynamicQuery from simple parameters.
query := queryUtils.DynamicQuery{
From: "data_retribusi",
Fields: []queryUtils.SelectField{{Expression: "*"}},
Sort: []queryUtils.SortField{{Column: "date_created", Order: "DESC"}},
From: "data_retribusi",
Fields: []queryUtils.SelectField{
{Expression: "id"},
{Expression: "status"},
{Expression: "sort"},
{Expression: "user_created"},
{Expression: "date_created"},
{Expression: "user_updated"},
{Expression: "date_updated"},
{Expression: "Jenis"},
{Expression: "Pelayanan"},
{Expression: "Dinas"},
{Expression: "Kelompok_obyek"},
{Expression: "Kode_tarif"},
{Expression: "Tarif"},
{Expression: "Satuan"},
{Expression: "Tarif_overtime"},
{Expression: "Satuan_overtime"},
{Expression: "Rekening_pokok"},
{Expression: "Rekening_denda"},
{Expression: "Uraian_1"},
{Expression: "Uraian_2"},
{Expression: "Uraian_3"},
},
Sort: []queryUtils.SortField{{Column: "date_created", Order: "DESC"}},
}
// Parse pagination
@@ -343,6 +366,7 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) {
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusi/{id} [get]
func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
id := c.Param("id")
if _, err := uuid.Parse(id); err != nil {
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
@@ -375,8 +399,30 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) {
defer cancel()
dynamicQuery := queryUtils.DynamicQuery{
From: "data_retribusi",
Fields: []queryUtils.SelectField{{Expression: "*"}},
From: "data_retribusi",
Fields: []queryUtils.SelectField{
{Expression: "id"},
{Expression: "status"},
{Expression: "sort"},
{Expression: "user_created"},
{Expression: "date_created"},
{Expression: "user_updated"},
{Expression: "date_updated"},
{Expression: "Jenis"},
{Expression: "Pelayanan"},
{Expression: "Dinas"},
{Expression: "Kelompok_obyek"},
{Expression: "Kode_tarif"},
{Expression: "Tarif"},
{Expression: "Satuan"},
{Expression: "Tarif_overtime"},
{Expression: "Satuan_overtime"},
{Expression: "Rekening_pokok"},
{Expression: "Rekening_denda"},
{Expression: "Uraian_1"},
{Expression: "Uraian_2"},
{Expression: "Uraian_3"},
},
Filters: []queryUtils.FilterGroup{{
Filters: []queryUtils.DynamicFilter{
{Column: "id", Operator: queryUtils.OpEqual, Value: id},
@@ -424,6 +470,7 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) {
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis/dynamic [get]
func (h *RetribusiHandler) GetRetribusiDynamic(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
parser := queryUtils.NewQueryParser().SetLimits(10, 100)
dynamicQuery, err := parser.ParseQuery(c.Request.URL.Query(), "data_retribusi")
if err != nil {
@@ -495,6 +542,7 @@ func (h *RetribusiHandler) GetRetribusiDynamic(c *gin.Context) {
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis [post]
func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
var req retribusi.RetribusiCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
@@ -599,6 +647,7 @@ func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) {
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusi/{id} [put]
func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
id := c.Param("id")
if _, err := uuid.Parse(id); err != nil {
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
@@ -748,6 +797,7 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) {
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusi/{id} [delete]
func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
id := c.Param("id")
if _, err := uuid.Parse(id); err != nil {
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
@@ -843,6 +893,7 @@ func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) {
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis/stats [get]
func (h *RetribusiHandler) GetRetribusiStats(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
// CHANGE: Try ambil dari cache terlebih dahulu
cacheKey := fmt.Sprintf("retribusi:stats:%s", c.Query("status"))
if cachedData, found := h.cache.Get(cacheKey); found {
@@ -898,6 +949,19 @@ func (h *RetribusiHandler) GetRetribusiStats(c *gin.Context) {
})
}
// GetWelcome godoc
// @Summary Get welcome message
// @Description Returns a welcome message and logs the request
// @Tags Retribusi
// @Accept json
// @Produce json
// @Success 200 {object} map[string]string "Welcome message"
// @Router /api/v1/retribusis/welcome [get]
func (h *RetribusiHandler) GetWelcome(c *gin.Context) {
logger.Info("Request received", map[string]interface{}{"method": c.Request.Method, "path": c.Request.URL.Path})
c.JSON(http.StatusOK, gin.H{"message": "Welcome to the Retribusi API Service!"})
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================

View File

@@ -0,0 +1,397 @@
package pasien
import (
"api-service/internal/models"
"database/sql"
"encoding/json"
"time"
)
// Pasien represents the data structure for the m_pasien table
// with proper null handling and optimized JSON marshaling
type Pasien struct {
ID int32 `json:"id" db:"id"`
Nomr sql.NullString `json:"nomr,omitempty" db:"nomr"`
Status sql.NullString `json:"status,omitempty" db:"status"`
Title sql.NullString `json:"title,omitempty" db:"title"`
Nama sql.NullString `json:"nama,omitempty" db:"nama"`
Tempat sql.NullString `json:"tempat,omitempty" db:"tempat"`
Tgllahir sql.NullTime `json:"tgllahir,omitempty" db:"tgllahir"`
Jeniskelamin sql.NullString `json:"jeniskelamin,omitempty" db:"jeniskelamin"`
Alamat sql.NullString `json:"alamat,omitempty" db:"alamat"`
Kelurahan sql.NullInt64 `json:"kelurahan,omitempty" db:"kelurahan"`
Kdkecamatan sql.NullInt32 `json:"kdkecamatan,omitempty" db:"kdkecamatan"`
Kota sql.NullInt32 `json:"kota,omitempty" db:"kota"`
Kdprovinsi sql.NullInt32 `json:"kdprovinsi,omitempty" db:"kdprovinsi"`
Agama sql.NullInt32 `json:"agama,omitempty" db:"agama"`
NoKartu sql.NullString `json:"noKartu,omitempty" db:"no_kartu"`
NoktpBaru sql.NullString `json:"noktpBaru,omitempty" db:"noktp_baru"`
CreatedAt sql.NullTime `json:"createdAt,omitempty" db:"created_at"`
UpdatedAt sql.NullTime `json:"updatedAt,omitempty" db:"updated_at"`
Idprovinsi int32 `json:"idprovinsi" db:"idprovinsi"`
Namaprovinsi sql.NullString `json:"namaprovinsi,omitempty" db:"namaprovinsi"`
Idkota int32 `json:"idkota" db:"idkota"`
Namakota sql.NullString `json:"namakota,omitempty" db:"namakota"`
Idkecamatan int64 `json:"idkecamatan" db:"idkecamatan"`
Namakecamatan sql.NullString `json:"namakecamatan,omitempty" db:"namakecamatan"`
Idkelurahan int64 `json:"idkelurahan" db:"idkelurahan"`
Namakelurahan sql.NullString `json:"namakelurahan,omitempty" db:"namakelurahan"`
}
// Custom JSON marshaling for Pasien so NULL values don't appear in response
func (r Pasien) MarshalJSON() ([]byte, error) {
type Alias Pasien
aux := &struct {
*Alias
Nomr *string `json:"nomr,omitempty"`
Status *string `json:"status,omitempty"`
Title *string `json:"title,omitempty"`
Nama *string `json:"nama,omitempty"`
Tempat *string `json:"tempat,omitempty"`
Tgllahir *time.Time `json:"tgllahir,omitempty"`
Jeniskelamin *string `json:"jeniskelamin,omitempty"`
Alamat *string `json:"alamat,omitempty"`
Kelurahan *int64 `json:"kelurahan,omitempty"`
Kdkecamatan *int32 `json:"kdkecamatan,omitempty"`
Kota *int32 `json:"kota,omitempty"`
Kdprovinsi *int32 `json:"kdprovinsi,omitempty"`
Agama *int32 `json:"agama,omitempty"`
NoKartu *string `json:"noKartu,omitempty"`
NoktpBaru *string `json:"noktpBaru,omitempty"`
CreatedAt *time.Time `json:"createdAt,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
Namaprovinsi *string `json:"namaprovinsi,omitempty"`
Namakota *string `json:"namakota,omitempty"`
Namakecamatan *string `json:"namakecamatan,omitempty"`
Namakelurahan *string `json:"namakelurahan,omitempty"`
}{
Alias: (*Alias)(&r),
}
if r.Nomr.Valid {
aux.Nomr = &r.Nomr.String
}
if r.Status.Valid {
aux.Status = &r.Status.String
}
if r.Title.Valid {
aux.Title = &r.Title.String
}
if r.Nama.Valid {
aux.Nama = &r.Nama.String
}
if r.Tempat.Valid {
aux.Tempat = &r.Tempat.String
}
if r.Tgllahir.Valid {
aux.Tgllahir = &r.Tgllahir.Time
}
if r.Jeniskelamin.Valid {
aux.Jeniskelamin = &r.Jeniskelamin.String
}
if r.Alamat.Valid {
aux.Alamat = &r.Alamat.String
}
if r.Kelurahan.Valid {
aux.Kelurahan = &r.Kelurahan.Int64
}
if r.Kdkecamatan.Valid {
aux.Kdkecamatan = &r.Kdkecamatan.Int32
}
if r.Kota.Valid {
aux.Kota = &r.Kota.Int32
}
if r.Kdprovinsi.Valid {
aux.Kdprovinsi = &r.Kdprovinsi.Int32
}
if r.Agama.Valid {
aux.Agama = &r.Agama.Int32
}
if r.NoKartu.Valid {
aux.NoKartu = &r.NoKartu.String
}
if r.NoktpBaru.Valid {
aux.NoktpBaru = &r.NoktpBaru.String
}
if r.CreatedAt.Valid {
aux.CreatedAt = &r.CreatedAt.Time
}
if r.UpdatedAt.Valid {
aux.UpdatedAt = &r.UpdatedAt.Time
}
if r.Namaprovinsi.Valid {
aux.Namaprovinsi = &r.Namaprovinsi.String
}
if r.Namakota.Valid {
aux.Namakota = &r.Namakota.String
}
if r.Namakecamatan.Valid {
aux.Namakecamatan = &r.Namakecamatan.String
}
if r.Namakelurahan.Valid {
aux.Namakelurahan = &r.Namakelurahan.String
}
return json.Marshal(aux)
}
// Helper method to safely get Nomr
func (r *Pasien) GetNomr() string {
if r.Nomr.Valid {
return r.Nomr.String
}
return ""
}
// Helper method to safely get Status
func (r *Pasien) GetStatus() string {
if r.Status.Valid {
return r.Status.String
}
return ""
}
// Helper method to safely get Title
func (r *Pasien) GetTitle() string {
if r.Title.Valid {
return r.Title.String
}
return ""
}
// Helper method to safely get Nama
func (r *Pasien) GetNama() string {
if r.Nama.Valid {
return r.Nama.String
}
return ""
}
// Helper method to safely get Tempat
func (r *Pasien) GetTempat() string {
if r.Tempat.Valid {
return r.Tempat.String
}
return ""
}
// Helper method to safely get Tgllahir
func (r *Pasien) GetTgllahir() time.Time {
if r.Tgllahir.Valid {
return r.Tgllahir.Time
}
return time.Time{}
}
// Helper method to safely get Jeniskelamin
func (r *Pasien) GetJeniskelamin() string {
if r.Jeniskelamin.Valid {
return r.Jeniskelamin.String
}
return ""
}
// Helper method to safely get Alamat
func (r *Pasien) GetAlamat() string {
if r.Alamat.Valid {
return r.Alamat.String
}
return ""
}
// Helper method to safely get Kelurahan
func (r *Pasien) GetKelurahan() int64 {
if r.Kelurahan.Valid {
return r.Kelurahan.Int64
}
return 0
}
// Helper method to safely get Kdkecamatan
func (r *Pasien) GetKdkecamatan() int32 {
if r.Kdkecamatan.Valid {
return r.Kdkecamatan.Int32
}
return 0
}
// Helper method to safely get Kota
func (r *Pasien) GetKota() int32 {
if r.Kota.Valid {
return r.Kota.Int32
}
return 0
}
// Helper method to safely get Kdprovinsi
func (r *Pasien) GetKdprovinsi() int32 {
if r.Kdprovinsi.Valid {
return r.Kdprovinsi.Int32
}
return 0
}
// Helper method to safely get Agama
func (r *Pasien) GetAgama() int32 {
if r.Agama.Valid {
return r.Agama.Int32
}
return 0
}
// Helper method to safely get NoKartu
func (r *Pasien) GetNoKartu() string {
if r.NoKartu.Valid {
return r.NoKartu.String
}
return ""
}
// Helper method to safely get NoktpBaru
func (r *Pasien) GetNoktpBaru() string {
if r.NoktpBaru.Valid {
return r.NoktpBaru.String
}
return ""
}
// Helper method to safely get CreatedAt
func (r *Pasien) GetCreatedAt() time.Time {
if r.CreatedAt.Valid {
return r.CreatedAt.Time
}
return time.Time{}
}
// Helper method to safely get UpdatedAt
func (r *Pasien) GetUpdatedAt() time.Time {
if r.UpdatedAt.Valid {
return r.UpdatedAt.Time
}
return time.Time{}
}
// Helper method to safely get Namaprovinsi
func (r *Pasien) GetNamaprovinsi() string {
if r.Namaprovinsi.Valid {
return r.Namaprovinsi.String
}
return ""
}
// Helper method to safely get Namakota
func (r *Pasien) GetNamakota() string {
if r.Namakota.Valid {
return r.Namakota.String
}
return ""
}
// Helper method to safely get Namakecamatan
func (r *Pasien) GetNamakecamatan() string {
if r.Namakecamatan.Valid {
return r.Namakecamatan.String
}
return ""
}
// Helper method to safely get Namakelurahan
func (r *Pasien) GetNamakelurahan() string {
if r.Namakelurahan.Valid {
return r.Namakelurahan.String
}
return ""
}
// Response struct for delete
type PasienDeleteResponse struct {
Message string `json:"message"`
ID string `json:"id"`
}
// Response struct for by age
type PasienAgeStatsResponse struct {
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
// Response struct for create
type PasienCreateResponse struct {
Message string `json:"message"`
Data *Pasien `json:"data"`
}
// Request struct for create
type PasienCreateRequest struct {
Status *string `json:"status" validate:"required,oneof=draft active inactive"`
ID *int32 `json:"id"`
Nomr *string `json:"nomr"`
Title *string `json:"title" validate:"required,min=1,max=100"`
Nama *string `json:"nama" validate:"required,min=1,max=100"`
Tempat *string `json:"tempat"`
Tgllahir *time.Time `json:"tgllahir"`
Jeniskelamin *string `json:"jeniskelamin" validate:"oneof=L P"`
Alamat *string `json:"alamat"`
Kelurahan *int64 `json:"kelurahan"`
Kdkecamatan *int32 `json:"kdkecamatan"`
Kota *int32 `json:"kota"`
Kdprovinsi *int32 `json:"kdprovinsi"`
Agama *int32 `json:"agama"`
NoKartu *string `json:"noKartu"`
NoktpBaru *string `json:"noktpBaru"`
}
// Response struct for GET list
type PasienGetResponse struct {
Message string `json:"message"`
Data []Pasien `json:"data"`
Meta models.MetaResponse `json:"meta"`
Summary *models.AggregateData `json:"summary,omitempty"`
}
// Response struct for update
type PasienUpdateResponse struct {
Message string `json:"message"`
Data *Pasien `json:"data"`
}
// Update request
type PasienUpdateRequest struct {
ID *int32 `json:"-" validate:"required"`
Status *string `json:"status" validate:"required,oneof=draft active inactive"`
Nomr *string `json:"nomr"`
Title *string `json:"title" validate:"omitempty,min=1,max=255"`
Nama *string `json:"nama" validate:"required,min=1,max=100"`
Tempat *string `json:"tempat"`
Tgllahir *time.Time `json:"tgllahir"`
Jeniskelamin *string `json:"jeniskelamin" validate:"oneof=L P"`
Alamat *string `json:"alamat"`
Kelurahan *int64 `json:"kelurahan"`
Kdkecamatan *int32 `json:"kdkecamatan"`
Kota *int32 `json:"kota"`
Kdprovinsi *int32 `json:"kdprovinsi"`
Agama *int32 `json:"agama"`
NoKartu *string `json:"noKartu"`
NoktpBaru *string `json:"noktpBaru"`
}
// Response struct for get by ID
type PasienGetByIDResponse struct {
Message string `json:"message"`
Data *Pasien `json:"data"`
}
// Response struct for get by nomr
type PasienGetByNomrResponse struct {
Message string `json:"message"`
Data []Pasien `json:"data"`
Meta models.MetaResponse `json:"meta"`
Summary *models.AggregateData `json:"summary,omitempty"`
}
// Filter struct for query parameters
type PasienFilter struct {
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"`
Status *string `json:"status,omitempty" form:"status"`
}

View File

@@ -5,6 +5,8 @@ import (
"api-service/internal/database"
authHandlers "api-service/internal/handlers/auth"
healthcheckHandlers "api-service/internal/handlers/healthcheck"
pasienPasienHandlers "api-service/internal/handlers/pasien"
retribusiHandlers "api-service/internal/handlers/retribusi"
"api-service/internal/middleware"
services "api-service/internal/services/auth"
@@ -121,6 +123,19 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
retribusiHandler.DeleteRetribusi(c)
})
}
// Pasien endpoints
pasienPasienHandler := pasienPasienHandlers.NewPasienHandler()
pasienPasienGroup := v1.Group("/pasien")
{
pasienPasienGroup.PUT("/:nomr", pasienPasienHandler.UpdatePasien)
pasienPasienGroup.POST("/", pasienPasienHandler.CreatePasien)
pasienPasienGroup.DELETE("/:nomr", pasienPasienHandler.DeletePasien)
pasienPasienGroup.GET("/dynamic", pasienPasienHandler.GetPasienDynamic)
pasienPasienGroup.GET("/", pasienPasienHandler.GetPasien)
pasienPasienGroup.GET("/by-age", pasienPasienHandler.GetPasienByAge)
pasienPasienGroup.GET("/:nomr", pasienPasienHandler.GetPasienByNomr)
pasienPasienGroup.GET("/by-location", pasienPasienHandler.GetPasienByLocation)
}
// =============================================================================
// PROTECTED ROUTES (Authentication Required)

View File

@@ -1812,6 +1812,24 @@ func (qp *QueryParser) parseSorting(values url.Values) ([]SortField, error) {
return sorts, nil
}
// ParseQueryWithDefaultFields parses URL query parameters into a DynamicQuery struct with default fields.
func (qp *QueryParser) ParseQueryWithDefaultFields(values url.Values, defaultTable string, defaultFields []string) (DynamicQuery, error) {
query, err := qp.ParseQuery(values, defaultTable)
if err != nil {
return query, err
}
// If no fields specified, use default fields
if len(query.Fields) == 0 || (len(query.Fields) == 1 && query.Fields[0].Expression == "*") {
query.Fields = make([]SelectField, len(defaultFields))
for i, field := range defaultFields {
query.Fields[i] = SelectField{Expression: field}
}
}
return query, nil
}
// =============================================================================
// MONGODB QUERY BUILDER
// =============================================================================

View File

@@ -0,0 +1,918 @@
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // PostgreSQL driver
"yourpackage/utils"
)
func main() {
// Inisialisasi koneksi database
db, err := sqlx.Connect("postgres", "user=postgres dbname=testdb sslmode=disable")
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer db.Close()
// Inisialisasi QueryBuilder
qb := utils.NewQueryBuilder(utils.DBTypePostgreSQL)
// Contoh penggunaan
simpleQueryExample(db, qb)
complexQueryExample(db, qb)
nestedJoinExample(db, qb)
multiJoinExample(db, qb)
commonQueriesExample(db, qb)
jsonQueryExample(db, qb)
windowFunctionExample(db, qb)
cteExample(db, qb)
unionExample(db, qb)
aggregateExample(db, qb)
}
func simpleQueryExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Simple Query Example ===")
// Query sederhana dengan filter
query := utils.DynamicQuery{
From: "users",
Fields: []utils.SelectField{
{Expression: "id", Alias: "user_id"},
{Expression: "name", Alias: "user_name"},
{Expression: "email"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpEqual, Value: "active"},
{Column: "created_at", Operator: utils.OpGreaterThanEqual, Value: time.Now().AddDate(0, -1, 0)},
},
LogicOp: "AND",
},
},
Sort: []utils.SortField{
{Column: "name", Order: "ASC"},
},
Limit: 10,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing simple query: %v", err)
return
}
fmt.Printf("Found %d users\n", len(results))
for _, user := range results {
fmt.Printf("User: %+v\n", user)
}
}
func complexQueryExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Complex Query Example ===")
// Query dengan nested filter dan berbagai operator
query := utils.DynamicQuery{
From: "orders",
Fields: []utils.SelectField{
{Expression: "id", Alias: "order_id"},
{Expression: "customer_id"},
{Expression: "total_amount"},
{Expression: "order_date"},
{Expression: "status"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpIn, Value: []string{"completed", "processing"}},
{Column: "total_amount", Operator: utils.OpGreaterThan, Value: 1000},
},
LogicOp: "AND",
},
{
Filters: []utils.DynamicFilter{
{Column: "order_date", Operator: utils.OpBetween, Value: []interface{}{time.Now().AddDate(0, -3, 0), time.Now()}},
{Column: "customer_id", Operator: utils.OpNotIn, Value: []int{1, 2, 3}},
},
LogicOp: "OR",
},
},
Sort: []utils.SortField{
{Column: "order_date", Order: "DESC"},
{Column: "total_amount", Order: "DESC"},
},
Limit: 20,
Offset: 10,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing complex query: %v", err)
return
}
fmt.Printf("Found %d orders\n", len(results))
for _, order := range results {
fmt.Printf("Order: %+v\n", order)
}
}
func nestedJoinExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Nested Join Example ===")
// Query dengan nested join
query := utils.DynamicQuery{
From: "customers",
Fields: []utils.SelectField{
{Expression: "customers.id", Alias: "customer_id"},
{Expression: "customers.name", Alias: "customer_name"},
{Expression: "orders.id", Alias: "order_id"},
{Expression: "orders.total_amount"},
{Expression: "order_items.product_id"},
{Expression: "order_items.quantity"},
{Expression: "products.name", Alias: "product_name"},
},
Joins: []utils.Join{
{
Type: "LEFT",
Table: "orders",
Alias: "orders",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "customers.id", Operator: utils.OpEqual, Value: "orders.customer_id"},
},
},
},
{
Type: "LEFT",
Table: "order_items",
Alias: "order_items",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "orders.id", Operator: utils.OpEqual, Value: "order_items.order_id"},
},
},
},
{
Type: "LEFT",
Table: "products",
Alias: "products",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "order_items.product_id", Operator: utils.OpEqual, Value: "products.id"},
},
},
},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "customers.status", Operator: utils.OpEqual, Value: "active"},
{Column: "orders.status", Operator: utils.OpEqual, Value: "completed"},
},
LogicOp: "AND",
},
},
Sort: []utils.SortField{
{Column: "customers.name", Order: "ASC"},
{Column: "orders.id", Order: "DESC"},
},
Limit: 50,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing nested join query: %v", err)
return
}
fmt.Printf("Found %d customer-order-product records\n", len(results))
for _, record := range results {
fmt.Printf("Record: %+v\n", record)
}
}
func multiJoinExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Multi Join Example ===")
// Query dengan multiple join types
query := utils.DynamicQuery{
From: "employees",
Fields: []utils.SelectField{
{Expression: "employees.id", Alias: "employee_id"},
{Expression: "employees.name", Alias: "employee_name"},
{Expression: "departments.name", Alias: "department_name"},
{Expression: "projects.name", Alias: "project_name"},
{Expression: "tasks.title", Alias: "task_title"},
{Expression: "task_assignments.assigned_date"},
},
Joins: []utils.Join{
{
Type: "INNER",
Table: "departments",
Alias: "departments",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "employees.department_id", Operator: utils.OpEqual, Value: "departments.id"},
},
},
},
{
Type: "LEFT",
Table: "task_assignments",
Alias: "task_assignments",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "employees.id", Operator: utils.OpEqual, Value: "task_assignments.employee_id"},
},
},
},
{
Type: "LEFT",
Table: "tasks",
Alias: "tasks",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "task_assignments.task_id", Operator: utils.OpEqual, Value: "tasks.id"},
},
},
},
{
Type: "LEFT",
Table: "projects",
Alias: "projects",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "tasks.project_id", Operator: utils.OpEqual, Value: "projects.id"},
},
},
},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "employees.status", Operator: utils.OpEqual, Value: "active"},
{Column: "departments.status", Operator: utils.OpEqual, Value: "active"},
},
LogicOp: "AND",
},
},
Sort: []utils.SortField{
{Column: "departments.name", Order: "ASC"},
{Column: "employees.name", Order: "ASC"},
{Column: "task_assignments.assigned_date", Order: "DESC"},
},
Limit: 100,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing multi join query: %v", err)
return
}
fmt.Printf("Found %d employee-task-project records\n", len(results))
for _, record := range results {
fmt.Printf("Record: %+v\n", record)
}
}
func commonQueriesExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Common Queries Example ===")
// 1. Query dengan LIKE/ILIKE
likeQuery := utils.DynamicQuery{
From: "products",
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "name"},
{Expression: "price"},
{Expression: "category"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "name", Operator: utils.OpILike, Value: "%laptop%"},
{Column: "category", Operator: utils.OpEqual, Value: "electronics"},
},
LogicOp: "AND",
},
},
Sort: []utils.SortField{
{Column: "price", Order: "ASC"},
},
Limit: 10,
}
var products []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, likeQuery, &products)
if err != nil {
log.Printf("Error executing LIKE query: %v", err)
} else {
fmt.Printf("Found %d products matching 'laptop'\n", len(products))
}
// 2. Query dengan pagination
page := 2
pageSize := 20
paginationQuery := utils.DynamicQuery{
From: "orders",
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "customer_id"},
{Expression: "total_amount"},
{Expression: "order_date"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpEqual, Value: "completed"},
},
},
},
Sort: []utils.SortField{
{Column: "order_date", Order: "DESC"},
},
Limit: pageSize,
Offset: (page - 1) * pageSize,
}
var orders []map[string]interface{}
err = qb.ExecuteQuery(context.Background(), db, paginationQuery, &orders)
if err != nil {
log.Printf("Error executing pagination query: %v", err)
} else {
fmt.Printf("Found %d orders on page %d\n", len(orders), page)
}
// 3. Query dengan NULL/NOT NULL
nullQuery := utils.DynamicQuery{
From: "customers",
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "name"},
{Expression: "email"},
{Expression: "phone"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "email", Operator: utils.OpNotNull},
{Column: "phone", Operator: utils.OpNull},
},
LogicOp: "AND",
},
},
Limit: 10,
}
var customers []map[string]interface{}
err = qb.ExecuteQuery(context.Background(), db, nullQuery, &customers)
if err != nil {
log.Printf("Error executing NULL query: %v", err)
} else {
fmt.Printf("Found %d customers with email but no phone\n", len(customers))
}
// 4. Query dengan BETWEEN
betweenQuery := utils.DynamicQuery{
From: "transactions",
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "account_id"},
{Expression: "amount"},
{Expression: "transaction_date"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "amount", Operator: utils.OpBetween, Value: []interface{}{100, 1000}},
{Column: "transaction_date", Operator: utils.OpBetween, Value: []interface{}{time.Now().AddDate(0, -1, 0), time.Now()}},
},
LogicOp: "AND",
},
},
Sort: []utils.SortField{
{Column: "transaction_date", Order: "DESC"},
},
Limit: 20,
}
var transactions []map[string]interface{}
err = qb.ExecuteQuery(context.Background(), db, betweenQuery, &transactions)
if err != nil {
log.Printf("Error executing BETWEEN query: %v", err)
} else {
fmt.Printf("Found %d transactions between $100 and $1000 in the last month\n", len(transactions))
}
}
func jsonQueryExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== JSON Query Example ===")
// Query dengan operasi JSON
query := utils.DynamicQuery{
From: "products",
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "name"},
{Expression: "price"},
{Expression: "attributes"},
},
JsonOperations: []utils.JsonOperation{
{
Type: "extract",
Column: "attributes",
Path: "$.color",
Alias: "color",
},
{
Type: "extract",
Column: "attributes",
Path: "$.size",
Alias: "size",
},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{
Column: "attributes",
Operator: utils.OpJsonContains,
Value: map[string]interface{}{"category": "electronics"},
Options: map[string]interface{}{"path": "$"},
},
},
},
},
Sort: []utils.SortField{
{Column: "name", Order: "ASC"},
},
Limit: 10,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing JSON query: %v", err)
return
}
fmt.Printf("Found %d products with JSON attributes\n", len(results))
for _, product := range results {
fmt.Printf("Product: %+v\n", product)
}
}
func windowFunctionExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Window Function Example ===")
// Query dengan window functions
query := utils.DynamicQuery{
From: "sales",
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "salesperson_id"},
{Expression: "amount"},
{Expression: "sale_date"},
},
WindowFunctions: []utils.WindowFunction{
{
Function: "ROW_NUMBER",
Over: "salesperson_id",
OrderBy: "amount DESC",
Alias: "sales_rank",
},
{
Function: "SUM",
Over: "salesperson_id",
OrderBy: "sale_date",
Frame: "ROWS UNBOUNDED PRECEDING",
Alias: "running_total",
},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "sale_date", Operator: utils.OpGreaterThanEqual, Value: time.Now().AddDate(0, -6, 0)},
},
},
},
Sort: []utils.SortField{
{Column: "salesperson_id", Order: "ASC"},
{Column: "amount", Order: "DESC"},
},
Limit: 50,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing window function query: %v", err)
return
}
fmt.Printf("Found %d sales records with window functions\n", len(results))
for _, sale := range results {
fmt.Printf("Sale: %+v\n", sale)
}
}
func cteExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== CTE Example ===")
// Query dengan CTE
query := utils.DynamicQuery{
CTEs: []utils.CTE{
{
Name: "monthly_sales",
Query: utils.DynamicQuery{
Fields: []utils.SelectField{
{Expression: "salesperson_id"},
{Expression: "EXTRACT(MONTH FROM sale_date) AS month"},
{Expression: "SUM(amount) AS total"},
},
From: "sales",
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "sale_date", Operator: utils.OpGreaterThanEqual, Value: time.Now().AddDate(-1, 0, 0)},
},
},
},
GroupBy: []string{"salesperson_id", "EXTRACT(MONTH FROM sale_date)"},
},
},
{
Name: "top_salespeople",
Query: utils.DynamicQuery{
Fields: []utils.SelectField{
{Expression: "salesperson_id"},
{Expression: "SUM(total) AS yearly_total"},
},
From: "monthly_sales",
GroupBy: []string{"salesperson_id"},
Having: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "SUM(total)", Operator: utils.OpGreaterThan, Value: 10000},
},
},
},
},
},
},
Fields: []utils.SelectField{
{Expression: "salespeople.id"},
{Expression: "salespeople.name"},
{Expression: "top_salespeople.yearly_total"},
},
From: "salespeople",
Joins: []utils.Join{
{
Type: "INNER",
Table: "top_salespeople",
Alias: "top_salespeople",
OnConditions: utils.FilterGroup{
Filters: []utils.DynamicFilter{
{Column: "salespeople.id", Operator: utils.OpEqual, Value: "top_salespeople.salesperson_id"},
},
},
},
},
Sort: []utils.SortField{
{Column: "top_salespeople.yearly_total", Order: "DESC"},
},
Limit: 10,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing CTE query: %v", err)
return
}
fmt.Printf("Found %d top salespeople\n", len(results))
for _, salesperson := range results {
fmt.Printf("Salesperson: %+v\n", salesperson)
}
}
func unionExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== UNION Example ===")
// Query dengan UNION
query := utils.DynamicQuery{
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "name"},
{Expression: "email"},
{Expression: "'customer' AS user_type"},
},
From: "customers",
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpEqual, Value: "active"},
},
},
},
Unions: []utils.Union{
{
Type: "UNION ALL",
Query: utils.DynamicQuery{
Fields: []utils.SelectField{
{Expression: "id"},
{Expression: "name"},
{Expression: "email"},
{Expression: "'employee' AS user_type"},
},
From: "employees",
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpEqual, Value: "active"},
},
},
},
},
},
},
Sort: []utils.SortField{
{Column: "name", Order: "ASC"},
},
Limit: 20,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing UNION query: %v", err)
return
}
fmt.Printf("Found %d users (customers + employees)\n", len(results))
for _, user := range results {
fmt.Printf("User: %+v\n", user)
}
}
func aggregateExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Aggregate Example ===")
// Query dengan fungsi agregasi
query := utils.DynamicQuery{
Fields: []utils.SelectField{
{Expression: "category"},
{Expression: "COUNT(*) AS product_count"},
{Expression: "AVG(price) AS avg_price"},
{Expression: "MIN(price) AS min_price"},
{Expression: "MAX(price) AS max_price"},
{Expression: "SUM(stock_quantity) AS total_stock"},
},
From: "products",
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpEqual, Value: "active"},
},
},
},
GroupBy: []string{"category"},
Having: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "COUNT(*)", Operator: utils.OpGreaterThan, Value: 5},
},
},
},
Sort: []utils.SortField{
{Column: "product_count", Order: "DESC"},
},
Limit: 10,
}
var results []map[string]interface{}
err := qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing aggregate query: %v", err)
return
}
fmt.Printf("Found %d product categories\n", len(results))
for _, category := range results {
fmt.Printf("Category: %+v\n", category)
}
}
func crudOperationsExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== CRUD Operations Example ===")
// INSERT
insertData := utils.InsertData{
Columns: []string{"name", "email", "status", "created_at"},
Values: []interface{}{"John Doe", "john@example.com", "active", time.Now()},
JsonValues: map[string]interface{}{
"preferences": map[string]interface{}{
"theme": "dark",
"language": "en",
},
},
}
result, err := qb.ExecuteInsert(context.Background(), db, "customers", insertData, "id")
if err != nil {
log.Printf("Error executing INSERT: %v", err)
return
}
id, err := result.LastInsertId()
if err != nil {
log.Printf("Error getting inserted ID: %v", err)
return
}
fmt.Printf("Inserted customer with ID: %d\n", id)
// UPDATE
updateData := utils.UpdateData{
Columns: []string{"name", "status"},
Values: []interface{}{"John Smith", "inactive"},
JsonUpdates: map[string]utils.JsonUpdate{
"preferences": {
Path: "$.theme",
Value: "light",
},
},
}
filters := []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "id", Operator: utils.OpEqual, Value: id},
},
},
}
result, err = qb.ExecuteUpdate(context.Background(), db, "customers", updateData, filters, "updated_at")
if err != nil {
log.Printf("Error executing UPDATE: %v", err)
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
log.Printf("Error getting rows affected: %v", err)
return
}
fmt.Printf("Updated %d customer(s)\n", rowsAffected)
// DELETE
result, err = qb.ExecuteDelete(context.Background(), db, "customers", filters)
if err != nil {
log.Printf("Error executing DELETE: %v", err)
return
}
rowsAffected, err = result.RowsAffected()
if err != nil {
log.Printf("Error getting rows affected: %v", err)
return
}
fmt.Printf("Deleted %d customer(s)\n", rowsAffected)
}
func mongoExample() {
fmt.Println("\n=== MongoDB Example ===")
// Inisialisasi koneksi MongoDB
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatalf("Failed to connect to MongoDB: %v", err)
}
defer client.Disconnect(context.Background())
db := client.Database("testdb")
collection := db.Collection("users")
// Inisialisasi MongoQueryBuilder
mqb := utils.NewMongoQueryBuilder()
// Query sederhana
query := utils.DynamicQuery{
Fields: []utils.SelectField{
{Expression: "name"},
{Expression: "email"},
{Expression: "status"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpEqual, Value: "active"},
{Column: "age", Operator: utils.OpGreaterThan, Value: 18},
},
LogicOp: "AND",
},
},
Sort: []utils.SortField{
{Column: "name", Order: "ASC"},
},
Limit: 10,
}
var results []map[string]interface{}
err = mqb.ExecuteFind(context.Background(), collection, query, &results)
if err != nil {
log.Printf("Error executing MongoDB query: %v", err)
return
}
fmt.Printf("Found %d users\n", len(results))
for _, user := range results {
fmt.Printf("User: %+v\n", user)
}
// Aggregation pipeline
aggQuery := utils.DynamicQuery{
Fields: []utils.SelectField{
{Expression: "department", Alias: "_id"},
{Expression: "COUNT(*)", Alias: "employee_count"},
{Expression: "AVG(salary)", Alias: "avg_salary"},
},
Filters: []utils.FilterGroup{
{
Filters: []utils.DynamicFilter{
{Column: "status", Operator: utils.OpEqual, Value: "active"},
},
},
},
GroupBy: []string{"department"},
Sort: []utils.SortField{
{Column: "employee_count", Order: "DESC"},
},
Limit: 10,
}
var aggResults []map[string]interface{}
err = mqb.ExecuteAggregate(context.Background(), collection, aggQuery, &aggResults)
if err != nil {
log.Printf("Error executing MongoDB aggregation: %v", err)
return
}
fmt.Printf("Found %d departments\n", len(aggResults))
for _, dept := range aggResults {
fmt.Printf("Department: %+v\n", dept)
}
}
func queryParserExample(db *sqlx.DB, qb *utils.QueryBuilder) {
fmt.Println("\n=== Query Parser Example ===")
// Inisialisasi QueryParser
qp := utils.NewQueryParser()
// Parse URL query parameters
values := url.Values{}
values.Add("fields", "id,name,email,status")
values.Add("filter[status][_eq]", "active")
values.Add("filter[created_at][_gte]", "2023-01-01")
values.Add("filter[age][_between]", "18,65")
values.Add("sort", "+name,-created_at")
values.Add("limit", "20")
values.Add("offset", "10")
// Parse query parameters into DynamicQuery
query, err := qp.ParseQuery(values, "users")
if err != nil {
log.Printf("Error parsing query: %v", err)
return
}
// Execute the parsed query
var results []map[string]interface{}
err = qb.ExecuteQuery(context.Background(), db, query, &results)
if err != nil {
log.Printf("Error executing parsed query: %v", err)
return
}
fmt.Printf("Found %d users using parsed query\n", len(results))
for _, user := range results {
fmt.Printf("User: %+v\n", user)
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -19,79 +19,109 @@ services:
table_name: "m_pasien"
# Define all columns once for reuse
columns:
- name: "nomr"
type: "varchar"
nullable: true
go_type: "string"
description: "Nomor Rekam Medis"
- name: "title"
type: "varchar"
nullable: true
go_type: "string"
description: "Gelar pasien (Tn, Ny, Sdr, dll)"
- name: "nama"
type: "varchar"
nullable: true
go_type: "string"
validation: "required,min=1,max=100"
description: "Nama lengkap pasien"
- name: "tempat"
type: "varchar"
nullable: true
go_type: "string"
description: "Tempat lahir pasien"
- name: "tgllahir"
type: "date"
nullable: true
go_type: "time.Time"
description: "Tanggal lahir pasien"
- name: "jeniskelamin"
type: "varchar"
nullable: true
go_type: "string"
validation: "oneof=L P"
description: "Jenis kelamin (L/P)"
- name: "alamat"
type: "varchar"
nullable: true
go_type: "string"
description: "Alamat lengkap pasien"
- name: "kelurahan"
type: "int8"
nullable: true
go_type: "int64"
description: "ID Kelurahan"
- name: "kdkecamatan"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Kecamatan"
- name: "kota"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Kota"
- name: "kdprovinsi"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Provinsi"
- name: "agama"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Agama"
- name: "no_kartu"
type: "varchar"
nullable: true
go_type: "string"
description: "Nomor kartu identitas"
- name: "noktp_baru"
type: "varchar"
nullable: true
go_type: "string"
description: "Nomor KTP baru"
schema:
columns:
- name: "id"
type: "serial4"
nullable: false
go_type: "int32"
primary_key: true
unique: true
description: "Primary key for schedule"
- name: "nomr"
type: "varchar"
nullable: true
go_type: "string"
searchable: true
unique: true
description: "Nomor Rekam Medis"
- name: "status"
type: "varchar"
nullable: true
go_type: "string"
description: "Status pasien (A = Aktif, I = Inaktif)"
- name: "title"
type: "varchar"
nullable: true
go_type: "string"
description: "Gelar pasien (Tn, Ny, Sdr, dll)"
- name: "nama"
type: "varchar"
nullable: true
go_type: "string"
validation: "required,min=1,max=100"
searchable: true
description: "Nama lengkap pasien"
- name: "tempat"
type: "varchar"
nullable: true
go_type: "string"
description: "Tempat lahir pasien"
- name: "tgllahir"
type: "date"
nullable: true
go_type: "time.Time"
description: "Tanggal lahir pasien"
- name: "jeniskelamin"
type: "varchar"
nullable: true
go_type: "string"
validation: "oneof=L P"
description: "Jenis kelamin (L/P)"
- name: "alamat"
type: "varchar"
nullable: true
go_type: "string"
description: "Alamat lengkap pasien"
- name: "kelurahan"
type: "int8"
nullable: true
go_type: "int64"
description: "ID Kelurahan"
- name: "kdkecamatan"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Kecamatan"
- name: "kota"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Kota"
- name: "kdprovinsi"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Provinsi"
- name: "agama"
type: "int4"
nullable: true
go_type: "int32"
description: "ID Agama"
- name: "no_kartu"
type: "varchar"
nullable: true
go_type: "string"
searchable: true
unique: true
description: "Nomor kartu identitas"
- name: "noktp_baru"
type: "varchar"
nullable: true
go_type: "string"
description: "Nomor KTP baru"
- name: "created_at"
type: "timestamp"
nullable: true
go_type: "time.Time"
system_field: true
description: "Tanggal pembuatan record"
- name: "updated_at"
type: "timestamp"
nullable: true
go_type: "time.Time"
system_field: true
description: "Tanggal update record"
# Define relationships with other tables
relationships:
@@ -167,6 +197,8 @@ services:
# Define endpoints with reusable configurations
endpoints:
list:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["GET"]
path: "/"
description: "Get list of pasien with pagination and filters"
@@ -182,19 +214,37 @@ services:
fields: "with_location_names"
response_model: "PasienGetResponse"
get_by_nomr:
get_by_id:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["GET"]
path: "/:nomr"
description: "Get pasien by NOMR"
summary: "Get Pasien by NOMR"
path: "/:id"
description: "Get pasien by ID"
summary: "Get Pasien by ID"
tags: ["Pasien"]
require_auth: true
cache_enabled: true
cache_ttl: 300
fields: "with_location_names"
response_model: "PasienGetByNOMRResponse"
response_model: "PasienGetByIDResponse"
get_by_nomr:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["GET"]
path: "/nomr/:nomr"
description: "Get pasien by Nomr"
summary: "Get Pasien by Nomr"
tags: ["Pasien"]
require_auth: true
cache_enabled: true
cache_ttl: 300
fields: "with_location_names"
response_model: "PasienGetByNomrResponse"
create:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["POST"]
path: "/"
description: "Create a new pasien"
@@ -206,6 +256,8 @@ services:
response_model: "PasienCreateResponse"
update:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["PUT"]
path: "/:nomr"
description: "Update an existing pasien"
@@ -217,16 +269,20 @@ services:
response_model: "PasienUpdateResponse"
delete:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["DELETE"]
path: "/:nomr"
description: "Delete a pasien"
summary: "Delete Pasien"
tags: ["Pasien"]
require_auth: true
soft_delete: false
soft_delete: true
response_model: "PasienDeleteResponse"
dynamic:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["GET"]
path: "/dynamic"
description: "Get pasien with dynamic filtering"
@@ -237,28 +293,34 @@ services:
fields: "with_location_names"
response_model: "PasienGetResponse"
search:
methods: ["GET"]
path: "/search"
description: "Search pasien by name or NOMR"
summary: "Search Pasien"
tags: ["Pasien"]
require_auth: true
has_search: true
fields: "with_location_names"
response_model: "PasienGetResponse"
# search:
# handler_folder: "pasien"
# handler_file: "pasien.go"
# methods: ["GET"]
# path: "/search"
# description: "Search pasien by name or NOMR"
# summary: "Search Pasien"
# tags: ["Pasien"]
# require_auth: true
# has_search: true
# fields: "with_location_names"
# response_model: "PasienGetResponse"
stats:
methods: ["GET"]
path: "/stats"
description: "Get pasien statistics"
summary: "Get Pasien Stats"
tags: ["Pasien"]
require_auth: true
has_stats: true
response_model: "AggregateData"
# stats:
# handler_folder: "pasien"
# handler_file: "pasien.go"
# methods: ["GET"]
# path: "/stats"
# description: "Get pasien statistics"
# summary: "Get Pasien Stats"
# tags: ["Pasien"]
# require_auth: true
# has_stats: true
# response_model: "AggregateData"
by_location:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["GET"]
path: "/by-location"
description: "Get pasien by location (provinsi, kota, kecamatan, kelurahan)"
@@ -270,6 +332,8 @@ services:
response_model: "PasienGetResponse"
by_age:
handler_folder: "pasien"
handler_file: "pasien.go"
methods: ["GET"]
path: "/by-age"
description: "Get pasien statistics by age group"
@@ -277,4 +341,201 @@ services:
tags: ["Pasien"]
require_auth: true
has_stats: true
response_model: "PasienAgeStatsResponse"
response_model: "PasienAgeStatsResponse"
# schedule:
# name: "Jadwal Dokter"
# category: "schedule"
# package: "schedule"
# description: "Jadwal Dokter management"
# base_url: ""
# timeout: 30
# retry_count: 3
# table_name: "daftar_jadwal_dokter"
# # Define all columns once for reuse
# schema:
# columns:
# - name: "id"
# type: "serial4"
# nullable: false
# go_type: "int32"
# primary_key: true
# description: "Primary key for schedule"
# - name: "hari"
# type: "int4"
# nullable: true
# go_type: "int32"
# description: "Day of week (1-7)"
# - name: "nama_hari"
# type: "varchar"
# nullable: true
# go_type: "string"
# searchable: true
# description: "Name of day"
# - name: "waktu"
# type: "varchar"
# nullable: true
# go_type: "string"
# searchable: true
# description: "Time schedule"
# - name: "dokter"
# type: "uuid"
# nullable: true
# go_type: "string"
# searchable: true
# description: "Doctor ID"
# - name: "spesialis"
# type: "int4"
# nullable: true
# go_type: "int32"
# description: "Specialization ID"
# - name: "sub_spesialis"
# type: "int4"
# nullable: true
# go_type: "int32"
# description: "Sub-specialization ID"
# - name: "status"
# type: "int4"
# nullable: true
# go_type: "int32"
# description: "Status (1=active, 0=inactive)"
# - name: "date_created"
# type: "timestamp"
# nullable: true
# go_type: "time.Time"
# system_field: true
# description: "Tanggal pembuatan record"
# - name: "date_updated"
# type: "timestamp"
# nullable: true
# go_type: "time.Time"
# system_field: true
# description: "Tanggal update record"
# - name: "user_created"
# type: "varchar"
# nullable: true
# go_type: "string"
# system_field: true
# description: "User yang membuat record"
# - name: "user_updated"
# type: "varchar"
# nullable: true
# go_type: "string"
# system_field: true
# description: "User yang mengupdate record"
# # Define reusable field groups
# field_groups:
# base_fields: ["id", "hari", "nama_hari", "waktu", "dokter"]
# all_fields: ["id", "hari", "nama_hari", "waktu", "dokter", "spesialis", "sub_spesialis", "status"]
# # Define endpoints with reusable configurations
# endpoints:
# list:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["GET"]
# path: "/"
# description: "Get list of schedule with pagination and filters"
# summary: "Get Schedule List"
# tags: ["Schedule"]
# require_auth: true
# cache_enabled: true
# cache_ttl: 300
# has_pagination: true
# has_filter: true
# has_search: true
# has_stats: true
# fields: "all_fields"
# response_model: "ScheduleGetResponse"
# get_by_id:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["GET"]
# path: "/:id"
# description: "Get schedule by ID"
# summary: "Get Schedule by ID"
# tags: ["Schedule"]
# require_auth: true
# cache_enabled: true
# cache_ttl: 300
# fields: "all_fields"
# response_model: "ScheduleGetByIDResponse"
# create:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["POST"]
# path: "/"
# description: "Create a new schedule"
# summary: "Create Schedule"
# tags: ["Schedule"]
# require_auth: true
# fields: "all_fields"
# request_model: "ScheduleCreateRequest"
# response_model: "ScheduleCreateResponse"
# update:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["PUT"]
# path: "/:id"
# description: "Update an existing schedule"
# summary: "Update Schedule"
# tags: ["Schedule"]
# require_auth: true
# fields: "all_fields"
# request_model: "ScheduleUpdateRequest"
# response_model: "ScheduleUpdateResponse"
# delete:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["DELETE"]
# path: "/:id"
# description: "Delete a schedule"
# summary: "Delete Schedule"
# tags: ["Schedule"]
# require_auth: true
# soft_delete: true
# response_model: "ScheduleDeleteResponse"
# dynamic:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["GET"]
# path: "/dynamic"
# description: "Get schedule with dynamic filtering"
# summary: "Get Schedule Dynamic"
# tags: ["Schedule"]
# require_auth: true
# has_dynamic: true
# fields: "all_fields"
# response_model: "ScheduleGetResponse"
# search:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["GET"]
# path: "/search"
# description: "Search schedule by name or doctor"
# summary: "Search Schedule"
# tags: ["Schedule"]
# require_auth: true
# has_search: true
# fields: "all_fields"
# response_model: "ScheduleGetResponse"
# stats:
# handler_folder: "schedule"
# handler_file: "schedule.go"
# methods: ["GET"]
# path: "/stats"
# description: "Get schedule statistics"
# summary: "Get Schedule Stats"
# tags: ["Schedule"]
# require_auth: true
# has_stats: true
# response_model: "AggregateData"