Perbaikan pembacaan data base
This commit is contained in:
@@ -70,6 +70,42 @@ func LoadConfig() *Config {
|
||||
}
|
||||
|
||||
func (c *Config) loadDatabaseConfigs() {
|
||||
// Simplified approach: Directly load from environment variables
|
||||
// This ensures we get the exact values specified in .env
|
||||
|
||||
// Primary database configuration
|
||||
c.Databases["primary"] = DatabaseConfig{
|
||||
Name: "primary",
|
||||
Type: getEnv("DB_CONNECTION", "postgres"),
|
||||
Host: getEnv("DB_HOST", "localhost"),
|
||||
Port: getEnvAsInt("DB_PORT", 5432),
|
||||
Username: getEnv("DB_USERNAME", ""),
|
||||
Password: getEnv("DB_PASSWORD", ""),
|
||||
Database: getEnv("DB_DATABASE", "satu_db"),
|
||||
Schema: getEnv("DB_SCHEMA", "public"),
|
||||
SSLMode: getEnv("DB_SSLMODE", "disable"),
|
||||
MaxOpenConns: getEnvAsInt("DB_MAX_OPEN_CONNS", 25),
|
||||
MaxIdleConns: getEnvAsInt("DB_MAX_IDLE_CONNS", 25),
|
||||
ConnMaxLifetime: parseDuration(getEnv("DB_CONN_MAX_LIFETIME", "5m")),
|
||||
}
|
||||
|
||||
// SATUDATA database configuration
|
||||
c.Databases["satudata"] = DatabaseConfig{
|
||||
Name: "satudata",
|
||||
Type: getEnv("SATUDATA_CONNECTION", "postgres"),
|
||||
Host: getEnv("SATUDATA_HOST", "localhost"),
|
||||
Port: getEnvAsInt("SATUDATA_PORT", 5432),
|
||||
Username: getEnv("SATUDATA_USERNAME", ""),
|
||||
Password: getEnv("SATUDATA_PASSWORD", ""),
|
||||
Database: getEnv("SATUDATA_DATABASE", "satu_db"),
|
||||
Schema: getEnv("SATUDATA_SCHEMA", "public"),
|
||||
SSLMode: getEnv("SATUDATA_SSLMODE", "disable"),
|
||||
MaxOpenConns: getEnvAsInt("SATUDATA_MAX_OPEN_CONNS", 25),
|
||||
MaxIdleConns: getEnvAsInt("SATUDATA_MAX_IDLE_CONNS", 25),
|
||||
ConnMaxLifetime: parseDuration(getEnv("SATUDATA_CONN_MAX_LIFETIME", "5m")),
|
||||
}
|
||||
|
||||
// Legacy support for backward compatibility
|
||||
envVars := os.Environ()
|
||||
dbConfigs := make(map[string]map[string]string)
|
||||
|
||||
@@ -100,35 +136,12 @@ func (c *Config) loadDatabaseConfigs() {
|
||||
dbConfigs[dbName][property] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Parse DB_ prefixed variables
|
||||
if strings.HasPrefix(key, "DB_") && !strings.Contains(key, "_REPLICA_") {
|
||||
segments := strings.Split(key, "_")
|
||||
if len(segments) >= 3 {
|
||||
dbName := strings.ToLower(segments[1])
|
||||
property := strings.ToLower(strings.Join(segments[2:], "_"))
|
||||
|
||||
if dbConfigs[dbName] == nil {
|
||||
dbConfigs[dbName] = make(map[string]string)
|
||||
}
|
||||
dbConfigs[dbName][property] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Parse legacy format (for backward compatibility)
|
||||
if strings.HasPrefix(key, "BLUEPRINT_DB_") {
|
||||
if dbConfigs["primary"] == nil {
|
||||
dbConfigs["primary"] = make(map[string]string)
|
||||
}
|
||||
property := strings.ToLower(strings.TrimPrefix(key, "BLUEPRINT_DB_"))
|
||||
dbConfigs["primary"][property] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Create DatabaseConfig from parsed configurations
|
||||
// Create DatabaseConfig from parsed configurations for additional databases
|
||||
for name, config := range dbConfigs {
|
||||
// Skip empty configurations or system configurations
|
||||
if name == "" || strings.Contains(name, "chrome_crashpad_pipe") {
|
||||
if name == "" || strings.Contains(name, "chrome_crashpad_pipe") || name == "primary" || name == "satudata" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -154,25 +167,8 @@ func (c *Config) loadDatabaseConfigs() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle legacy format
|
||||
if name == "primary" && dbConfig.Type == "postgres" && dbConfig.Host == "localhost" {
|
||||
dbConfig.Host = getEnv("BLUEPRINT_DB_HOST", "localhost")
|
||||
dbConfig.Port = getEnvAsInt("BLUEPRINT_DB_PORT", 5432)
|
||||
dbConfig.Username = getEnv("BLUEPRINT_DB_USERNAME", "postgres")
|
||||
dbConfig.Password = getEnv("BLUEPRINT_DB_PASSWORD", "postgres")
|
||||
dbConfig.Database = getEnv("BLUEPRINT_DB_DATABASE", "api_service")
|
||||
dbConfig.Schema = getEnv("BLUEPRINT_DB_SCHEMA", "public")
|
||||
}
|
||||
|
||||
c.Databases[name] = dbConfig
|
||||
}
|
||||
|
||||
// Add specific databases from .env if not already parsed
|
||||
c.addSpecificDatabase("db", "postgres")
|
||||
c.addSpecificDatabase("simrs", "postgres")
|
||||
c.addSpecificDatabase("antrian", "mysql")
|
||||
c.addSpecificDatabase("satudata", "postgres")
|
||||
c.addSpecificDatabase("mongodb_dev", "mongodb")
|
||||
}
|
||||
|
||||
func (c *Config) loadReadReplicaConfigs() {
|
||||
|
||||
@@ -4,13 +4,22 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"log" // Import runtime package
|
||||
|
||||
// Import debug package
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"api-service/internal/config"
|
||||
|
||||
_ "github.com/jackc/pgx/v5" // Import pgx driver
|
||||
_ "gorm.io/driver/postgres" // Import GORM PostgreSQL driver
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // MySQL driver for database/sql
|
||||
_ "gorm.io/driver/mysql" // GORM MySQL driver
|
||||
_ "gorm.io/driver/sqlserver" // GORM SQL Server driver
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
@@ -64,7 +73,9 @@ func New(cfg *config.Config) Service {
|
||||
readBalancer: make(map[string]int),
|
||||
}
|
||||
|
||||
// Load configurations from config
|
||||
log.Println("Initializing database service...") // Log when the initialization starts
|
||||
// log.Printf("Current Goroutine ID: %d", runtime.NumGoroutine()) // Log the number of goroutines
|
||||
// log.Printf("Stack Trace: %s", debug.Stack()) // Log the stack trace
|
||||
dbManager.loadFromConfig(cfg)
|
||||
|
||||
// Initialize all databases
|
||||
@@ -106,6 +117,15 @@ func (s *service) addDatabase(name string, config config.DatabaseConfig) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
log.Printf("=== Database Connection Debug ===")
|
||||
log.Printf("Database: %s", name)
|
||||
log.Printf("Type: %s", config.Type)
|
||||
log.Printf("Host: %s", config.Host)
|
||||
log.Printf("Port: %d", config.Port)
|
||||
log.Printf("Database: %s", config.Database)
|
||||
log.Printf("Username: %s", config.Username)
|
||||
log.Printf("SSLMode: %s", config.SSLMode)
|
||||
|
||||
var db *sql.DB
|
||||
var err error
|
||||
|
||||
@@ -127,9 +147,12 @@ func (s *service) addDatabase(name string, config config.DatabaseConfig) error {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("❌ Error connecting to database %s: %v", name, err)
|
||||
log.Printf(" Database: %s@%s:%d/%s", config.Username, config.Host, config.Port, config.Database)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("✅ Successfully connected to database: %s", name)
|
||||
return s.configureSQLDB(name, db, config.MaxOpenConns, config.MaxIdleConns, config.ConnMaxLifetime)
|
||||
}
|
||||
|
||||
@@ -408,14 +431,27 @@ func (s *service) Health() map[string]map[string]string {
|
||||
|
||||
// GetDB returns a specific SQL database connection by name
|
||||
func (s *service) GetDB(name string) (*sql.DB, error) {
|
||||
log.Printf("Attempting to get database connection for: %s", name)
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
db, exists := s.sqlDatabases[name]
|
||||
if !exists {
|
||||
log.Printf("Error: database %s not found", name) // Log the error
|
||||
return nil, fmt.Errorf("database %s not found", name)
|
||||
}
|
||||
|
||||
log.Printf("Current connection pool state for %s: Open: %d, In Use: %d, Idle: %d",
|
||||
name, db.Stats().OpenConnections, db.Stats().InUse, db.Stats().Idle)
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
// db, exists := s.sqlDatabases[name]
|
||||
// if !exists {
|
||||
// log.Printf("Error: database %s not found", name) // Log the error
|
||||
// return nil, fmt.Errorf("database %s not found", name)
|
||||
// }
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -533,6 +569,3 @@ func (s *service) Close() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Import necessary packages
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Mock for the database service
|
||||
type MockService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockService) Health() map[string]map[string]string {
|
||||
args := m.Called()
|
||||
return args.Get(0).(map[string]map[string]string)
|
||||
}
|
||||
|
||||
func (m *MockService) GetDB(name string) (*sql.DB, error) {
|
||||
args := m.Called(name)
|
||||
return args.Get(0).(*sql.DB), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockService) Close() error {
|
||||
return m.Called().Error(0)
|
||||
}
|
||||
|
||||
func (m *MockService) ListDBs() []string {
|
||||
args := m.Called()
|
||||
return args.Get(0).([]string)
|
||||
}
|
||||
|
||||
func (m *MockService) GetDBType(name string) (DatabaseType, error) {
|
||||
args := m.Called(name)
|
||||
return args.Get(0).(DatabaseType), args.Error(1)
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
srv := New()
|
||||
if srv == nil {
|
||||
t.Fatal("New() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
srv := New()
|
||||
|
||||
stats := srv.Health()
|
||||
|
||||
// Since we don't have any databases configured in test, we expect empty stats
|
||||
if len(stats) == 0 {
|
||||
t.Log("No databases configured, health check returns empty stats")
|
||||
return
|
||||
}
|
||||
|
||||
// If we have databases, check their health
|
||||
for dbName, dbStats := range stats {
|
||||
if dbStats["status"] != "up" {
|
||||
t.Errorf("database %s status is not up: %s", dbName, dbStats["status"])
|
||||
}
|
||||
if err, ok := dbStats["error"]; ok && err != "" {
|
||||
t.Errorf("database %s has error: %s", dbName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
srv := New()
|
||||
|
||||
if srv.Close() != nil {
|
||||
t.Fatalf("expected Close() to return nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Test for loading database configurations
|
||||
func TestLoadDatabaseConfigs(t *testing.T) {
|
||||
// Set environment variables for testing
|
||||
os.Setenv("DB_TEST_TYPE", "postgres")
|
||||
os.Setenv("DB_TEST_HOST", "localhost")
|
||||
os.Setenv("DB_TEST_PORT", "5432")
|
||||
os.Setenv("DB_TEST_DATABASE", "testdb")
|
||||
os.Setenv("DB_TEST_USERNAME", "testuser")
|
||||
os.Setenv("DB_TEST_PASSWORD", "testpass")
|
||||
|
||||
configs := loadDatabaseConfigs()
|
||||
if len(configs) == 0 {
|
||||
t.Fatal("Expected database configurations to be loaded")
|
||||
}
|
||||
|
||||
if configs[0].Type != "postgres" {
|
||||
t.Errorf("Expected database type to be postgres, got %s", configs[0].Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for connection pooling settings
|
||||
func TestConnectionPooling(t *testing.T) {
|
||||
srv := New()
|
||||
// Check health after loading configurations
|
||||
stats := srv.Health()
|
||||
if len(stats) == 0 {
|
||||
t.Fatal("Expected databases to be configured, but found none")
|
||||
}
|
||||
|
||||
db, err := srv.GetDB("testdb")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get database: %v", err)
|
||||
}
|
||||
|
||||
if db.Stats().MaxOpenConnections != 10 {
|
||||
t.Errorf("Expected max open connections to be 10, got %d", db.Stats().MaxOpenConnections)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for error handling during connection
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
srv := New()
|
||||
// Check health to see if it handles errors
|
||||
stats := srv.Health()
|
||||
if len(stats) > 0 {
|
||||
t.Fatal("Expected no databases to be configured, but found some")
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"api-service/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ExampleHandler handles example GET and POST services
|
||||
type ExampleHandler struct{}
|
||||
|
||||
// NewExampleHandler creates a new ExampleHandler
|
||||
func NewExampleHandler() *ExampleHandler {
|
||||
return &ExampleHandler{}
|
||||
}
|
||||
|
||||
// GetExample godoc
|
||||
// @Summary Example GET service
|
||||
// @Description Returns a simple message for GET request
|
||||
// @Tags example
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.ExampleGetResponse "Example GET response"
|
||||
// @Router /api/v1/example [get]
|
||||
func (h *ExampleHandler) GetExample(c *gin.Context) {
|
||||
response := models.ExampleGetResponse{
|
||||
Message: "This is a GET example response",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// PostExample godoc
|
||||
// @Summary Example POST service
|
||||
// @Description Accepts a JSON payload and returns a response with an ID
|
||||
// @Tags example
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.ExamplePostRequest true "Example POST request"
|
||||
// @Success 200 {object} models.ExamplePostResponse "Example POST response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Router /api/v1/example [post]
|
||||
func (h *ExampleHandler) PostExample(c *gin.Context) {
|
||||
var req models.ExamplePostRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := models.ExamplePostResponse{
|
||||
ID: uuid.NewString(),
|
||||
Message: "Received data for " + req.Name,
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"api-service/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// HealthHandler handles health check endpoints
|
||||
type HealthHandler struct{}
|
||||
|
||||
// NewHealthHandler creates a new HealthHandler
|
||||
func NewHealthHandler() *HealthHandler {
|
||||
return &HealthHandler{}
|
||||
}
|
||||
|
||||
// GetHealth godoc
|
||||
// @Summary Health check endpoint
|
||||
// @Description Returns the health status of the API service
|
||||
// @Tags health
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.HealthResponse "Health status"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /health [get]
|
||||
func (h *HealthHandler) GetHealth(c *gin.Context) {
|
||||
health := models.HealthResponse{
|
||||
Status: "healthy",
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Details: map[string]string{
|
||||
"service": "api-service",
|
||||
"version": "1.0.0",
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, health)
|
||||
}
|
||||
|
||||
// HelloWorld godoc
|
||||
// @Summary Hello World endpoint
|
||||
// @Description Returns a hello world message
|
||||
// @Tags root
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.HelloWorldResponse "Hello world message"
|
||||
// @Router / [get]
|
||||
func (h *HealthHandler) HelloWorld(c *gin.Context) {
|
||||
response := models.HelloWorldResponse{
|
||||
Message: "Hello World",
|
||||
Version: "1.0.0",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"api-service/internal/models"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ProductHandler handles product services
|
||||
type ProductHandler struct{}
|
||||
|
||||
// NewProductHandler creates a new ProductHandler
|
||||
func NewProductHandler() *ProductHandler {
|
||||
return &ProductHandler{}
|
||||
}
|
||||
|
||||
// GetProduct godoc
|
||||
// @Summary Get product
|
||||
// @Description Returns a list of products
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.ProductGetResponse "Product GET response"
|
||||
// @Router /api/v1/products [get]
|
||||
func (h *ProductHandler) GetProduct(c *gin.Context) {
|
||||
response := models.ProductGetResponse{
|
||||
Message: "List of products",
|
||||
Data: []string{"Product 1", "Product 2"},
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetProductByID godoc
|
||||
// @Summary Get product by ID
|
||||
// @Description Returns a single product by ID
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Product ID"
|
||||
// @Success 200 {object} models.ProductGetByIDResponse "Product GET by ID response"
|
||||
// @Failure 404 {object} models.ErrorResponse "Product not found"
|
||||
// @Router /api/v1/products/{id} [get]
|
||||
func (h *ProductHandler) GetProductByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
response := models.ProductGetByIDResponse{
|
||||
ID: id,
|
||||
Message: "Product details",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreateProduct godoc
|
||||
// @Summary Create product
|
||||
// @Description Creates a new product
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.ProductCreateRequest true "Product creation request"
|
||||
// @Success 201 {object} models.ProductCreateResponse "Product created successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Router /api/v1/products [post]
|
||||
func (h *ProductHandler) CreateProduct(c *gin.Context) {
|
||||
var req models.ProductCreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := models.ProductCreateResponse{
|
||||
ID: uuid.NewString(),
|
||||
Message: "Product created successfully",
|
||||
Data: req,
|
||||
}
|
||||
c.JSON(http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// UpdateProduct godoc
|
||||
// @Summary Update product
|
||||
// @Description Updates an existing product
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Product ID"
|
||||
// @Param request body models.ProductUpdateRequest true "Product update request"
|
||||
// @Success 200 {object} models.ProductUpdateResponse "Product updated successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 404 {object} models.ErrorResponse "Product not found"
|
||||
// @Router /api/v1/products/{id} [put]
|
||||
func (h *ProductHandler) UpdateProduct(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req models.ProductUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := models.ProductUpdateResponse{
|
||||
ID: id,
|
||||
Message: "Product updated successfully",
|
||||
Data: req,
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// DeleteProduct godoc
|
||||
// @Summary Delete product
|
||||
// @Description Deletes a product by ID
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Product ID"
|
||||
// @Success 200 {object} models.ProductDeleteResponse "Product deleted successfully"
|
||||
// @Failure 404 {object} models.ErrorResponse "Product not found"
|
||||
// @Router /api/v1/products/{id} [delete]
|
||||
func (h *ProductHandler) DeleteProduct(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
response := models.ProductDeleteResponse{
|
||||
ID: id,
|
||||
Message: "Product deleted successfully",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
1015
internal/handlers/retribusi/retribusi.go
Normal file
1015
internal/handlers/retribusi/retribusi.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
models "api-service/internal/models/retribusi"
|
||||
"net/http"
|
||||
|
||||
"api-service/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package models
|
||||
|
||||
// ExampleGetResponse represents the response for the GET example service
|
||||
type ExampleGetResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ExamplePostRequest represents the request body for the POST example service
|
||||
type ExamplePostRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Age int `json:"age" binding:"required"`
|
||||
}
|
||||
|
||||
// ExamplePostResponse represents the response for the POST example service
|
||||
type ExamplePostResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package models
|
||||
|
||||
// HealthResponse represents the health check response
|
||||
type HealthResponse struct {
|
||||
Status string `json:"status"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Details map[string]string `json:"details"`
|
||||
}
|
||||
|
||||
// HelloWorldResponse represents the hello world response
|
||||
type HelloWorldResponse struct {
|
||||
Message string `json:"message"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// ErrorResponse represents an error response
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
// SuccessResponse represents a generic success response
|
||||
type SuccessResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Pagination represents pagination metadata
|
||||
type Pagination struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Total int `json:"total"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// PaginatedResponse represents a paginated response
|
||||
type PaginatedResponse struct {
|
||||
Data interface{} `json:"data"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package models
|
||||
|
||||
// ProductGetResponse represents the response for GET products
|
||||
type ProductGetResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ProductGetByIDResponse represents the response for GET product by ID
|
||||
type ProductGetByIDResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ProductCreateRequest represents the request for creating product
|
||||
type ProductCreateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
// Add more fields as needed
|
||||
}
|
||||
|
||||
// ProductCreateResponse represents the response for creating product
|
||||
type ProductCreateResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ProductUpdateRequest represents the request for updating product
|
||||
type ProductUpdateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
// Add more fields as needed
|
||||
}
|
||||
|
||||
// ProductUpdateResponse represents the response for updating product
|
||||
type ProductUpdateResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ProductDeleteResponse represents the response for deleting product
|
||||
type ProductDeleteResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ErrorResponse represents an error response
|
||||
// type ErrorResponse struct {
|
||||
// Error string `json:"error"`
|
||||
// }
|
||||
@@ -1,42 +0,0 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// Product represents the product domain model
|
||||
type Product struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ProductCreateRequest represents the request for creating a product
|
||||
type ProductCreateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// ProductUpdateRequest represents the request for updating a product
|
||||
type ProductUpdateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// ProductResponse represents the response for product operations
|
||||
type ProductResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ProductsResponse represents the response for listing products
|
||||
type ProductsResponse struct {
|
||||
Data []*Product `json:"data"`
|
||||
}
|
||||
281
internal/models/retribusi/retribusi.go
Normal file
281
internal/models/retribusi/retribusi.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Retribusi represents the data structure for the retribusi table
|
||||
// with proper null handling and optimized JSON marshaling
|
||||
type Retribusi struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
Status string `json:"status" db:"status"`
|
||||
Sort sql.NullInt32 `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"`
|
||||
Jenis sql.NullString `json:"jenis,omitempty" db:"Jenis"`
|
||||
Pelayanan sql.NullString `json:"pelayanan,omitempty" db:"Pelayanan"`
|
||||
Dinas sql.NullString `json:"dinas,omitempty" db:"Dinas"`
|
||||
KelompokObyek sql.NullString `json:"kelompok_obyek,omitempty" db:"Kelompok_obyek"`
|
||||
KodeTarif sql.NullString `json:"kode_tarif,omitempty" db:"Kode_tarif"`
|
||||
Tarif sql.NullString `json:"tarif,omitempty" db:"Tarif"`
|
||||
Satuan sql.NullString `json:"satuan,omitempty" db:"Satuan"`
|
||||
TarifOvertime sql.NullString `json:"tarif_overtime,omitempty" db:"Tarif_overtime"`
|
||||
SatuanOvertime sql.NullString `json:"satuan_overtime,omitempty" db:"Satuan_overtime"`
|
||||
RekeningPokok sql.NullString `json:"rekening_pokok,omitempty" db:"Rekening_pokok"`
|
||||
RekeningDenda sql.NullString `json:"rekening_denda,omitempty" db:"Rekening_denda"`
|
||||
Uraian1 sql.NullString `json:"uraian_1,omitempty" db:"Uraian_1"`
|
||||
Uraian2 sql.NullString `json:"uraian_2,omitempty" db:"Uraian_2"`
|
||||
Uraian3 sql.NullString `json:"uraian_3,omitempty" db:"Uraian_3"`
|
||||
}
|
||||
|
||||
// Custom JSON marshaling untuk Retribusi agar NULL values tidak muncul di response
|
||||
func (r Retribusi) MarshalJSON() ([]byte, error) {
|
||||
type Alias Retribusi
|
||||
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"`
|
||||
Jenis *string `json:"jenis,omitempty"`
|
||||
Pelayanan *string `json:"pelayanan,omitempty"`
|
||||
Dinas *string `json:"dinas,omitempty"`
|
||||
KelompokObyek *string `json:"kelompok_obyek,omitempty"`
|
||||
KodeTarif *string `json:"kode_tarif,omitempty"`
|
||||
Tarif *string `json:"tarif,omitempty"`
|
||||
Satuan *string `json:"satuan,omitempty"`
|
||||
TarifOvertime *string `json:"tarif_overtime,omitempty"`
|
||||
SatuanOvertime *string `json:"satuan_overtime,omitempty"`
|
||||
RekeningPokok *string `json:"rekening_pokok,omitempty"`
|
||||
RekeningDenda *string `json:"rekening_denda,omitempty"`
|
||||
Uraian1 *string `json:"uraian_1,omitempty"`
|
||||
Uraian2 *string `json:"uraian_2,omitempty"`
|
||||
Uraian3 *string `json:"uraian_3,omitempty"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(&r),
|
||||
}
|
||||
|
||||
// Convert sql.Null* to pointers
|
||||
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.Jenis.Valid {
|
||||
aux.Jenis = &r.Jenis.String
|
||||
}
|
||||
if r.Pelayanan.Valid {
|
||||
aux.Pelayanan = &r.Pelayanan.String
|
||||
}
|
||||
if r.Dinas.Valid {
|
||||
aux.Dinas = &r.Dinas.String
|
||||
}
|
||||
if r.KelompokObyek.Valid {
|
||||
aux.KelompokObyek = &r.KelompokObyek.String
|
||||
}
|
||||
if r.KodeTarif.Valid {
|
||||
aux.KodeTarif = &r.KodeTarif.String
|
||||
}
|
||||
if r.Tarif.Valid {
|
||||
aux.Tarif = &r.Tarif.String
|
||||
}
|
||||
if r.Satuan.Valid {
|
||||
aux.Satuan = &r.Satuan.String
|
||||
}
|
||||
if r.TarifOvertime.Valid {
|
||||
aux.TarifOvertime = &r.TarifOvertime.String
|
||||
}
|
||||
if r.SatuanOvertime.Valid {
|
||||
aux.SatuanOvertime = &r.SatuanOvertime.String
|
||||
}
|
||||
if r.RekeningPokok.Valid {
|
||||
aux.RekeningPokok = &r.RekeningPokok.String
|
||||
}
|
||||
if r.RekeningDenda.Valid {
|
||||
aux.RekeningDenda = &r.RekeningDenda.String
|
||||
}
|
||||
if r.Uraian1.Valid {
|
||||
aux.Uraian1 = &r.Uraian1.String
|
||||
}
|
||||
if r.Uraian2.Valid {
|
||||
aux.Uraian2 = &r.Uraian2.String
|
||||
}
|
||||
if r.Uraian3.Valid {
|
||||
aux.Uraian3 = &r.Uraian3.String
|
||||
}
|
||||
|
||||
return json.Marshal(aux)
|
||||
}
|
||||
|
||||
// Helper methods untuk mendapatkan nilai yang aman
|
||||
func (r *Retribusi) GetJenis() string {
|
||||
if r.Jenis.Valid {
|
||||
return r.Jenis.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *Retribusi) GetDinas() string {
|
||||
if r.Dinas.Valid {
|
||||
return r.Dinas.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *Retribusi) GetTarif() string {
|
||||
if r.Tarif.Valid {
|
||||
return r.Tarif.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Response struct untuk GET by ID - diperbaiki struktur
|
||||
type RetribusiGetByIDResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *Retribusi `json:"data"`
|
||||
}
|
||||
|
||||
// Request struct untuk create - dioptimalkan dengan validasi
|
||||
type RetribusiCreateRequest struct {
|
||||
Status string `json:"status" validate:"required,oneof=draft active inactive"`
|
||||
Jenis *string `json:"jenis,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Pelayanan *string `json:"pelayanan,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Dinas *string `json:"dinas,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
KelompokObyek *string `json:"kelompok_obyek,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
KodeTarif *string `json:"kode_tarif,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Uraian1 *string `json:"uraian_1,omitempty"`
|
||||
Uraian2 *string `json:"uraian_2,omitempty"`
|
||||
Uraian3 *string `json:"uraian_3,omitempty"`
|
||||
Tarif *string `json:"tarif,omitempty" validate:"omitempty,numeric"`
|
||||
Satuan *string `json:"satuan,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
TarifOvertime *string `json:"tarif_overtime,omitempty" validate:"omitempty,numeric"`
|
||||
SatuanOvertime *string `json:"satuan_overtime,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
RekeningPokok *string `json:"rekening_pokok,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
RekeningDenda *string `json:"rekening_denda,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
}
|
||||
|
||||
// Response struct untuk create
|
||||
type RetribusiCreateResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *Retribusi `json:"data"`
|
||||
}
|
||||
|
||||
// Update request - sama seperti create tapi dengan ID
|
||||
type RetribusiUpdateRequest struct {
|
||||
ID string `json:"-" validate:"required,uuid4"` // ID dari URL path
|
||||
Status string `json:"status" validate:"required,oneof=draft active inactive"`
|
||||
Jenis *string `json:"jenis,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Pelayanan *string `json:"pelayanan,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Dinas *string `json:"dinas,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
KelompokObyek *string `json:"kelompok_obyek,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
KodeTarif *string `json:"kode_tarif,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Uraian1 *string `json:"uraian_1,omitempty"`
|
||||
Uraian2 *string `json:"uraian_2,omitempty"`
|
||||
Uraian3 *string `json:"uraian_3,omitempty"`
|
||||
Tarif *string `json:"tarif,omitempty" validate:"omitempty,numeric"`
|
||||
Satuan *string `json:"satuan,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
TarifOvertime *string `json:"tarif_overtime,omitempty" validate:"omitempty,numeric"`
|
||||
SatuanOvertime *string `json:"satuan_overtime,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
RekeningPokok *string `json:"rekening_pokok,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
RekeningDenda *string `json:"rekening_denda,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
}
|
||||
|
||||
// Response struct untuk update
|
||||
type RetribusiUpdateResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *Retribusi `json:"data"`
|
||||
}
|
||||
|
||||
// Response struct untuk delete
|
||||
type RetribusiDeleteResponse struct {
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// Enhanced GET response dengan pagination dan aggregation
|
||||
type RetribusiGetResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data []Retribusi `json:"data"`
|
||||
Meta MetaResponse `json:"meta"`
|
||||
Summary *AggregateData `json:"summary,omitempty"`
|
||||
}
|
||||
|
||||
// Metadata untuk pagination - dioptimalkan
|
||||
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 untuk 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"`
|
||||
ByDinas map[string]int `json:"by_dinas,omitempty"`
|
||||
ByJenis map[string]int `json:"by_jenis,omitempty"`
|
||||
LastUpdated *time.Time `json:"last_updated,omitempty"`
|
||||
CreatedToday int `json:"created_today"`
|
||||
UpdatedToday int `json:"updated_today"`
|
||||
}
|
||||
|
||||
// Error response yang konsisten
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Filter struct untuk query parameters
|
||||
type RetribusiFilter struct {
|
||||
Status *string `json:"status,omitempty" form:"status"`
|
||||
Jenis *string `json:"jenis,omitempty" form:"jenis"`
|
||||
Dinas *string `json:"dinas,omitempty" form:"dinas"`
|
||||
KelompokObyek *string `json:"kelompok_obyek,omitempty" form:"kelompok_obyek"`
|
||||
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 untuk validasi
|
||||
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
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
model "api-service/internal/models/product"
|
||||
)
|
||||
|
||||
// Repository defines the interface for product data operations
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, product *model.Product) error
|
||||
GetByID(ctx context.Context, id string) (*model.Product, error)
|
||||
GetAll(ctx context.Context) ([]*model.Product, error)
|
||||
Update(ctx context.Context, product *model.Product) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
// repository implements the Repository interface
|
||||
type repository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewRepository creates a new product repository
|
||||
func NewRepository(db *sql.DB) Repository {
|
||||
return &repository{db: db}
|
||||
}
|
||||
|
||||
// Create adds a new product to the database
|
||||
func (r *repository) Create(ctx context.Context, product *model.Product) error {
|
||||
query := `
|
||||
INSERT INTO products (id, name, description, price, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
|
||||
_, err := r.db.ExecContext(ctx, query,
|
||||
product.ID,
|
||||
product.Name,
|
||||
product.Description,
|
||||
product.Price,
|
||||
product.CreatedAt,
|
||||
product.UpdatedAt,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetByID retrieves a product by its ID
|
||||
func (r *repository) GetByID(ctx context.Context, id string) (*model.Product, error) {
|
||||
query := `
|
||||
SELECT id, name, description, price, created_at, updated_at
|
||||
FROM products
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
var product model.Product
|
||||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||||
&product.ID,
|
||||
&product.Name,
|
||||
&product.Description,
|
||||
&product.Price,
|
||||
&product.CreatedAt,
|
||||
&product.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// GetAll retrieves all products
|
||||
func (r *repository) GetAll(ctx context.Context) ([]*model.Product, error) {
|
||||
query := `
|
||||
SELECT id, name, description, price, created_at, updated_at
|
||||
FROM products
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var products []*model.Product
|
||||
for rows.Next() {
|
||||
var product model.Product
|
||||
err := rows.Scan(
|
||||
&product.ID,
|
||||
&product.Name,
|
||||
&product.Description,
|
||||
&product.Price,
|
||||
&product.CreatedAt,
|
||||
&product.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
products = append(products, &product)
|
||||
}
|
||||
|
||||
return products, nil
|
||||
}
|
||||
|
||||
// Update updates an existing product
|
||||
func (r *repository) Update(ctx context.Context, product *model.Product) error {
|
||||
query := `
|
||||
UPDATE products
|
||||
SET name = ?, description = ?, price = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
_, err := r.db.ExecContext(ctx, query,
|
||||
product.Name,
|
||||
product.Description,
|
||||
product.Price,
|
||||
product.UpdatedAt,
|
||||
product.ID,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete removes a product from the database
|
||||
func (r *repository) Delete(ctx context.Context, id string) error {
|
||||
query := `DELETE FROM products WHERE id = ?`
|
||||
_, err := r.db.ExecContext(ctx, query, id)
|
||||
return err
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
retribusiHandlers "api-service/internal/handlers/retribusi"
|
||||
"net/http"
|
||||
|
||||
"api-service/internal/config"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
|
||||
authHandlers "api-service/internal/handlers/auth"
|
||||
componentHandlers "api-service/internal/handlers/component"
|
||||
)
|
||||
|
||||
// RegisterRoutes registers all API routes for version 1
|
||||
@@ -35,10 +35,6 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||
v1 := router.Group("/api/v1")
|
||||
{
|
||||
// Public routes (no authentication required)
|
||||
// Health endpoints
|
||||
healthHandler := componentHandlers.NewHealthHandler()
|
||||
v1.GET("/health", healthHandler.GetHealth)
|
||||
v1.GET("/", healthHandler.HelloWorld)
|
||||
|
||||
// Authentication routes
|
||||
authHandler := authHandlers.NewAuthHandler(authService)
|
||||
@@ -53,25 +49,19 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||
v1.POST("/token/generate", tokenHandler.GenerateToken)
|
||||
v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
|
||||
|
||||
|
||||
// Retribusi endpoints
|
||||
retribusiHandler := retribusiHandlers.NewRetribusiHandler()
|
||||
v1.GET("/retribusis", retribusiHandler.GetRetribusi)
|
||||
v1.GET("/retribusi/:id", retribusiHandler.GetRetribusiByID)
|
||||
v1.POST("/retribusis", retribusiHandler.CreateRetribusi)
|
||||
v1.PUT("/retribusi/:id", retribusiHandler.UpdateRetribusi)
|
||||
v1.DELETE("/retribusi/:id", retribusiHandler.DeleteRetribusi)
|
||||
|
||||
// Protected routes (require authentication)
|
||||
|
||||
|
||||
protected := v1.Group("/")
|
||||
protected.Use(middleware.JWTAuthMiddleware(authService))
|
||||
{
|
||||
// Product endpoints
|
||||
productHandler := componentHandlers.NewProductHandler()
|
||||
protected.GET("/products", productHandler.GetProduct)
|
||||
protected.GET("/products/:id", productHandler.GetProductByID)
|
||||
protected.POST("/products", productHandler.CreateProduct)
|
||||
protected.PUT("/products/:id", productHandler.UpdateProduct)
|
||||
protected.DELETE("/products/:id", productHandler.DeleteProduct)
|
||||
|
||||
// Example endpoints
|
||||
exampleHandler := componentHandlers.NewExampleHandler()
|
||||
protected.GET("/example", exampleHandler.GetExample)
|
||||
protected.POST("/example", exampleHandler.PostExample)
|
||||
|
||||
// WebSocket endpoint
|
||||
protected.GET("/websocket", WebSocketHandler)
|
||||
protected.GET("/webservice", WebServiceHandler)
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
v1 "api-service/internal/routes/v1"
|
||||
)
|
||||
|
||||
var dbService database.Service // Global variable to hold the database service instance
|
||||
|
||||
type Server struct {
|
||||
port int
|
||||
db database.Service
|
||||
@@ -29,9 +31,13 @@ func NewServer() *http.Server {
|
||||
port = cfg.Server.Port
|
||||
}
|
||||
|
||||
if dbService == nil { // Check if the database service is already initialized
|
||||
dbService = database.New(cfg) // Initialize only once
|
||||
}
|
||||
|
||||
NewServer := &Server{
|
||||
port: port,
|
||||
db: database.New(cfg),
|
||||
db: dbService, // Use the global database service instance
|
||||
}
|
||||
|
||||
// Declare Server config
|
||||
|
||||
Reference in New Issue
Block a user