This commit is contained in:
@@ -5,11 +5,12 @@ import (
|
||||
"api-service/internal/database"
|
||||
models "api-service/internal/models"
|
||||
"api-service/internal/models/qris"
|
||||
"api-service/internal/utils/validation"
|
||||
utils "api-service/internal/utils/filters"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -69,7 +70,7 @@ func NewQrisHandler() *QrisHandler {
|
||||
// @Success 200 {object} qris.QrisGetResponse "Success response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/qriss [get]
|
||||
// @Router /qris [get]
|
||||
func (h *QrisHandler) GetQris(c *gin.Context) {
|
||||
// Parse pagination parameters
|
||||
limit, offset, err := h.parsePaginationParams(c)
|
||||
@@ -118,7 +119,7 @@ func (h *QrisHandler) GetQris(c *gin.Context) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := h.fetchQriss(ctx, dbConn, filter, limit, offset)
|
||||
result, err := h.fetchQris(ctx, dbConn, filter, limit, offset)
|
||||
mu.Lock()
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("failed to fetch data: %w", err)
|
||||
@@ -177,16 +178,16 @@ func (h *QrisHandler) GetQris(c *gin.Context) {
|
||||
// @Tags Qris
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Qris ID (UUID)"
|
||||
// @Param id path string true "Qris ID (Int serial4)"
|
||||
// @Success 200 {object} qris.QrisGetByIDResponse "Success response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
||||
// @Failure 404 {object} models.ErrorResponse "Qris not found"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/qris/{id} [get]
|
||||
// @Router /qris/{id} [get]
|
||||
func (h *QrisHandler) GetQrisByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
// Validate UUID format
|
||||
// Validate Int ID format
|
||||
intID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
||||
@@ -220,6 +221,54 @@ func (h *QrisHandler) GetQrisByID(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetQrisByIP godoc
|
||||
// @Summary Get Qris by IP
|
||||
// @Description Returns a single qris by IP
|
||||
// @Tags Qris
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param ip path string true "IP address"
|
||||
// @Success 200 {object} qris.QrisGetByIPResponse "Success response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Invalid IP address format"
|
||||
// @Failure 404 {object} models.ErrorResponse "Qris not found"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /qris/ip/{ip} [get]
|
||||
func (h *QrisHandler) GetQrisByIP(c *gin.Context) {
|
||||
ip := c.Param("ip")
|
||||
|
||||
// Validate IP address format
|
||||
if net.ParseIP(ip) == nil {
|
||||
h.respondError(c, "Invalid IP address format", fmt.Errorf("invalid IP: %s", ip), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
dbConn, err := h.db.GetDB("simrs_backup")
|
||||
if err != nil {
|
||||
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
item, err := h.getQrisByIP(ctx, dbConn, ip)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
h.respondError(c, "Qris not found", err, http.StatusNotFound)
|
||||
} else {
|
||||
h.logAndRespondError(c, "Failed to get qris", err, http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response := qris.QrisGetByIPResponse{
|
||||
Message: "qris details retrieved successfully",
|
||||
Data: item,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetQrisStats godoc
|
||||
// @Summary Get qris statistics
|
||||
// @Description Returns comprehensive statistics about qris data
|
||||
@@ -229,7 +278,7 @@ func (h *QrisHandler) GetQrisByID(c *gin.Context) {
|
||||
// @Param status query string false "Filter statistics by status"
|
||||
// @Success 200 {object} models.AggregateData "Statistics data"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/qriss/stats [get]
|
||||
// @Router /qris/stats [get]
|
||||
func (h *QrisHandler) GetQrisStats(c *gin.Context) {
|
||||
dbConn, err := h.db.GetDB("simrs_backup")
|
||||
if err != nil {
|
||||
@@ -255,11 +304,24 @@ func (h *QrisHandler) GetQrisStats(c *gin.Context) {
|
||||
|
||||
// Database operations
|
||||
func (h *QrisHandler) getQrisByID(ctx context.Context, dbConn *sql.DB, id int) (*qris.Qris, error) {
|
||||
query := "SELECT id, status, created_at, updated_at, display_name FROM t_qrdata WHERE id = $1 AND status IS NOT NULL"
|
||||
query := "SELECT id, status, created_at, updated_at, display_name, display_amount, qrvalue, ip, display_nobill, posdevice FROM t_qrdata WHERE id = $1 AND status != '0'"
|
||||
row := dbConn.QueryRowContext(ctx, query, id)
|
||||
|
||||
var item qris.Qris
|
||||
err := row.Scan(&item.ID, &item.Status, &item.CreatedAt, &item.UpdatedAt, &item.DisplayName)
|
||||
err := row.Scan(&item.ID, &item.Status, &item.CreatedAt, &item.UpdatedAt, &item.DisplayName, &item.DisplayAmount, &item.QrValue, &item.IPAddress, &item.DisplayNoBill, &item.PosDevice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (h *QrisHandler) getQrisByIP(ctx context.Context, dbConn *sql.DB, ip string) (*qris.Qris, error) {
|
||||
query := "SELECT id, status, created_at, updated_at, display_name, display_amount, qrvalue, ip, display_nobill, posdevice FROM t_qrdata WHERE ip = $1 AND status != '0'"
|
||||
row := dbConn.QueryRowContext(ctx, query, ip)
|
||||
|
||||
var item qris.Qris
|
||||
err := row.Scan(&item.ID, &item.Status, &item.CreatedAt, &item.UpdatedAt, &item.DisplayName, &item.DisplayAmount, &item.QrValue, &item.IPAddress, &item.DisplayNoBill, &item.PosDevice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -298,7 +360,7 @@ func (h *QrisHandler) getQrisByID(ctx context.Context, dbConn *sql.DB, id int) (
|
||||
return &item, nil
|
||||
}*/
|
||||
|
||||
func (h *QrisHandler) deleteQris(ctx context.Context, dbConn *sql.DB, id string) error {
|
||||
/*func (h *QrisHandler) deleteQris(ctx context.Context, dbConn *sql.DB, id string) error {
|
||||
now := time.Now()
|
||||
query := "UPDATE data_qris_qris SET status = 'deleted', updated_at = $2 WHERE id = $1 AND status != 'deleted'"
|
||||
|
||||
@@ -317,16 +379,16 @@ func (h *QrisHandler) deleteQris(ctx context.Context, dbConn *sql.DB, id string)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}*/
|
||||
|
||||
func (h *QrisHandler) fetchQriss(ctx context.Context, dbConn *sql.DB, filter qris.QrisFilter, limit, offset int) ([]qris.Qris, error) {
|
||||
func (h *QrisHandler) fetchQris(ctx context.Context, dbConn *sql.DB, filter qris.QrisFilter, limit, offset int) ([]qris.Qris, error) {
|
||||
whereClause, args := h.buildWhereClause(filter)
|
||||
query := fmt.Sprintf("SELECT id, status, created_at, updated_at, display_name FROM t_qrdata WHERE %s ORDER BY created_at DESC NULLS LAST LIMIT $%d OFFSET $%d", whereClause, len(args)+1, len(args)+2)
|
||||
query := fmt.Sprintf("SELECT id, status, created_at, updated_at, display_name, display_amount, qrvalue, ip, display_nobill, posdevice FROM t_qrdata WHERE %s ORDER BY created_at DESC NULLS LAST LIMIT $%d OFFSET $%d", whereClause, len(args)+1, len(args)+2)
|
||||
args = append(args, limit, offset)
|
||||
|
||||
rows, err := dbConn.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch qriss query failed: %w", err)
|
||||
return nil, fmt.Errorf("fetch qris query failed: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
@@ -343,7 +405,7 @@ func (h *QrisHandler) fetchQriss(ctx context.Context, dbConn *sql.DB, filter qri
|
||||
return nil, fmt.Errorf("rows iteration error: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully fetched %d qriss with filters applied", len(items))
|
||||
log.Printf("Successfully fetched %d qris with filters applied", len(items))
|
||||
return items, nil
|
||||
}
|
||||
|
||||
@@ -355,9 +417,14 @@ func (h *QrisHandler) scanQris(rows *sql.Rows) (qris.Qris, error) {
|
||||
err := rows.Scan(
|
||||
&item.ID,
|
||||
&item.Status,
|
||||
&item.CreatedAt, //.Time, &item.CreatedAt.Valid, // sql.NullTime
|
||||
&item.UpdatedAt, //.Time, &item.UpdatedAt.Valid, // sql.NullTime
|
||||
&item.DisplayName, //.String, &item.DisplayName.Valid, // sql.NullString
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
&item.DisplayName,
|
||||
&item.DisplayAmount,
|
||||
&item.QrValue,
|
||||
&item.IPAddress,
|
||||
&item.DisplayNoBill,
|
||||
&item.PosDevice,
|
||||
)
|
||||
|
||||
return item, err
|
||||
@@ -368,6 +435,7 @@ func (h *QrisHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter
|
||||
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM t_qrdata WHERE %s", whereClause)
|
||||
if err := dbConn.QueryRowContext(ctx, countQuery, args...).Scan(total); err != nil {
|
||||
return fmt.Errorf("total count query failed: %w", err)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -562,7 +630,7 @@ func (h *QrisHandler) parseFilterParams(c *gin.Context) qris.QrisFilter {
|
||||
|
||||
// Build WHERE clause dengan filter parameters
|
||||
func (h *QrisHandler) buildWhereClause(filter qris.QrisFilter) (string, []interface{}) {
|
||||
conditions := []string{"status IS NOT NULL"}
|
||||
conditions := []string{"status != '0'"}
|
||||
args := []interface{}{}
|
||||
paramCount := 1
|
||||
|
||||
@@ -573,11 +641,16 @@ func (h *QrisHandler) buildWhereClause(filter qris.QrisFilter) (string, []interf
|
||||
}
|
||||
|
||||
if filter.Search != nil {
|
||||
searchCondition := fmt.Sprintf("display_name ILIKE $%d", paramCount)
|
||||
conditions = append(conditions, searchCondition)
|
||||
searchTerm := "%" + *filter.Search + "%"
|
||||
args = append(args, searchTerm)
|
||||
paramCount++
|
||||
searchFields := []string{"ip", "display_name", "posdevice"}
|
||||
searchConditions := make([]string, 0, len(searchFields))
|
||||
for _, field := range searchFields {
|
||||
searchConditions = append(searchConditions, fmt.Sprintf("%s ILIKE $%d", field, paramCount))
|
||||
args = append(args, searchTerm)
|
||||
paramCount++
|
||||
}
|
||||
// Combine with OR
|
||||
conditions = append(conditions, fmt.Sprintf("(%s)", strings.Join(searchConditions, " OR ")))
|
||||
}
|
||||
|
||||
if filter.DateFrom != nil {
|
||||
@@ -674,7 +747,257 @@ func (h *QrisHandler) calculateMeta(limit, offset, total int) models.MetaRespons
|
||||
}*/
|
||||
|
||||
// GetLastSubmissionTime example
|
||||
func (h *QrisHandler) getLastSubmissionTimeExample(ctx context.Context, dbConn *sql.DB, identifier string) (*time.Time, error) {
|
||||
/*func (h *QrisHandler) getLastSubmissionTimeExample(ctx context.Context, dbConn *sql.DB, identifier string) (*time.Time, error) {
|
||||
validator := validation.NewDuplicateValidator(dbConn)
|
||||
return validator.GetLastSubmissionTime(ctx, "t_qrdata", "id", "created_at", identifier)
|
||||
}*/
|
||||
|
||||
// fetchDynamic executes dynamic query
|
||||
func (h *QrisHandler) fetchDynamic(ctx context.Context, dbConn *sql.DB, query utils.DynamicQuery) ([]qris.Qris, int, error) {
|
||||
// Setup query builder
|
||||
builder := utils.NewQueryBuilder("t_qrdata").
|
||||
SetColumnMapping(map[string]string{
|
||||
"id": "id",
|
||||
"status": "status",
|
||||
"created_at": "created_at",
|
||||
"updated_at": "updated_at",
|
||||
"display_name": "display_name",
|
||||
"display_amount": "display_amount",
|
||||
"qrvalue": "qrvalue",
|
||||
"ip": "ip",
|
||||
"display_nobill": "display_nobill",
|
||||
"posdevice": "posdevice",
|
||||
}).
|
||||
SetAllowedColumns([]string{
|
||||
"id", "status", "created_at", "updated_at", "display_name",
|
||||
"display_amount", "qrvalue", "ip", "display_nobill", "posdevice",
|
||||
})
|
||||
|
||||
// Add default filter to exclude deleted records
|
||||
query.Filters = append([]utils.FilterGroup{{
|
||||
Filters: []utils.DynamicFilter{{
|
||||
Column: "status",
|
||||
Operator: utils.OpNotEqual,
|
||||
Value: "0",
|
||||
}},
|
||||
LogicOp: "AND",
|
||||
}}, query.Filters...)
|
||||
|
||||
// Execute concurrent queries
|
||||
var (
|
||||
items []qris.Qris
|
||||
total int
|
||||
wg sync.WaitGroup
|
||||
errChan = make(chan error, 2)
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
// Fetch total count
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
countQuery := query
|
||||
countQuery.Limit = 0
|
||||
countQuery.Offset = 0
|
||||
countSQL, countArgs, err := builder.BuildCountQuery(countQuery)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("failed to build count query: %w", err)
|
||||
return
|
||||
}
|
||||
if err := dbConn.QueryRowContext(ctx, countSQL, countArgs...).Scan(&total); err != nil {
|
||||
errChan <- fmt.Errorf("failed to get total count: %w", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Fetch main data
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mainSQL, mainArgs, err := builder.BuildQuery(query)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("failed to build main query: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := dbConn.QueryContext(ctx, mainSQL, mainArgs...)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("failed to execute main query: %w", err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []qris.Qris
|
||||
for rows.Next() {
|
||||
item, err := h.scanQris(rows)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("failed to scan search: %w", err)
|
||||
return
|
||||
}
|
||||
results = append(results, item)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
errChan <- fmt.Errorf("rows iteration error: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
items = results
|
||||
mu.Unlock()
|
||||
}()
|
||||
|
||||
// Wait for all goroutines
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
// Check for errors
|
||||
for err := range errChan {
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// GetSearchDynamic godoc
|
||||
// @Summary Get search with dynamic filtering
|
||||
// @Description Returns search with advanced dynamic filtering like Directus
|
||||
// @Tags Search
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param fields query string false "Fields to select (e.g., fields=*.*)"
|
||||
// @Param filter[column][operator] query string false "Dynamic filters (e.g., filter[name][_eq]=value)"
|
||||
// @Param sort query string false "Sort fields (e.g., sort=date_created,-name)"
|
||||
// @Param limit query int false "Limit" default(10)
|
||||
// @Param offset query int false "Offset" default(0)
|
||||
// @Success 200 {object} qris.QrisGetResponse "Success response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /qris/search/dynamic [get]
|
||||
func (h *QrisHandler) GetSearchDynamic(c *gin.Context) {
|
||||
// Parse query parameters
|
||||
parser := utils.NewQueryParser().SetLimits(10, 100)
|
||||
dynamicQuery, err := parser.ParseQuery(c.Request.URL.Query())
|
||||
if err != nil {
|
||||
h.respondError(c, "Invalid query parameters", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get database connection
|
||||
dbConn, err := h.db.GetDB("simrs_backup")
|
||||
if err != nil {
|
||||
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Create context with timeout
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute query with dynamic filtering
|
||||
items, total, err := h.fetchDynamic(ctx, dbConn, dynamicQuery)
|
||||
if err != nil {
|
||||
h.logAndRespondError(c, "Failed to fetch data", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Build response
|
||||
meta := h.calculateMeta(dynamicQuery.Limit, dynamicQuery.Offset, total)
|
||||
response := qris.QrisGetResponse{
|
||||
Message: "Data qris berhasil diambil",
|
||||
Data: items,
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// SearchAdvanced provides advanced search capabilities
|
||||
func (h *QrisHandler) SearchAdvanced(c *gin.Context) {
|
||||
// Parse complex search parameters
|
||||
searchQuery := c.Query("q")
|
||||
if searchQuery == "" {
|
||||
h.respondError(c, "Search query is required", fmt.Errorf("empty search query"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Build dynamic query for search
|
||||
query := utils.DynamicQuery{
|
||||
Fields: []string{"*"},
|
||||
Filters: []utils.FilterGroup{{
|
||||
Filters: []utils.DynamicFilter{
|
||||
{
|
||||
Column: "status",
|
||||
Operator: utils.OpNotEqual,
|
||||
Value: "0",
|
||||
},
|
||||
{
|
||||
Column: "ip",
|
||||
Operator: utils.OpContains,
|
||||
Value: searchQuery,
|
||||
LogicOp: "OR",
|
||||
},
|
||||
{
|
||||
Column: "display_name",
|
||||
Operator: utils.OpContains,
|
||||
Value: searchQuery,
|
||||
LogicOp: "OR",
|
||||
},
|
||||
{
|
||||
Column: "posdevice",
|
||||
Operator: utils.OpContains,
|
||||
Value: searchQuery,
|
||||
LogicOp: "OR",
|
||||
},
|
||||
},
|
||||
LogicOp: "AND",
|
||||
}},
|
||||
Sort: []utils.SortField{{
|
||||
Column: "created_at",
|
||||
Order: "DESC",
|
||||
}},
|
||||
Limit: 20,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
// Parse pagination if provided
|
||||
if limit := c.Query("limit"); limit != "" {
|
||||
if l, err := strconv.Atoi(limit); err == nil && l > 0 && l <= 100 {
|
||||
query.Limit = l
|
||||
}
|
||||
}
|
||||
if offset := c.Query("offset"); offset != "" {
|
||||
if o, err := strconv.Atoi(offset); err == nil && o >= 0 {
|
||||
query.Offset = o
|
||||
}
|
||||
}
|
||||
|
||||
// Get database connection
|
||||
dbConn, err := h.db.GetDB("simrs_backup")
|
||||
if err != nil {
|
||||
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute search
|
||||
items, total, err := h.fetchDynamic(ctx, dbConn, query)
|
||||
if err != nil {
|
||||
h.logAndRespondError(c, "Search failed", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Build response
|
||||
meta := h.calculateMeta(query.Limit, query.Offset, total)
|
||||
response := qris.QrisGetResponse{
|
||||
Message: fmt.Sprintf("Search results for '%s'", searchQuery),
|
||||
Data: items,
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func NewRetribusiHandler() *RetribusiHandler {
|
||||
// @Success 200 {object} modelsretribusi.RetribusiGetResponse "Success response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/retribusis [get]
|
||||
// @Router /retribusis [get]
|
||||
func (h *RetribusiHandler) GetRetribusi(c *gin.Context) {
|
||||
// Parse pagination parameters
|
||||
limit, offset, err := h.parsePaginationParams(c)
|
||||
@@ -189,7 +189,7 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) {
|
||||
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
||||
// @Failure 404 {object} models.ErrorResponse "Retribusi not found"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/retribusi/{id} [get]
|
||||
// @Router /retribusi/{id} [get]
|
||||
func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
@@ -240,7 +240,7 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) {
|
||||
// @Success 200 {object} modelsretribusi.RetribusiGetResponse "Success response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/retribusis/dynamic [get]
|
||||
// @Router /retribusi/dynamic [get]
|
||||
func (h *RetribusiHandler) GetRetribusiDynamic(c *gin.Context) {
|
||||
// Parse query parameters
|
||||
parser := utils.NewQueryParser().SetLimits(10, 100)
|
||||
@@ -503,7 +503,7 @@ func (h *RetribusiHandler) SearchRetribusiAdvanced(c *gin.Context) {
|
||||
// @Success 201 {object} modelsretribusi.RetribusiCreateResponse "Retribusi created successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/retribusis [post]
|
||||
// @Router /retribusis [post]
|
||||
func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) {
|
||||
var req modelsretribusi.RetribusiCreateRequest
|
||||
|
||||
@@ -559,7 +559,7 @@ func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) {
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
||||
// @Failure 404 {object} models.ErrorResponse "Retribusi not found"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/retribusi/{id} [put]
|
||||
// @Router /retribusi/{id} [put]
|
||||
func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
@@ -622,7 +622,7 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) {
|
||||
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
||||
// @Failure 404 {object} models.ErrorResponse "Retribusi not found"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/retribusi/{id} [delete]
|
||||
// @Router /retribusi/{id} [delete]
|
||||
func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
@@ -668,7 +668,7 @@ func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) {
|
||||
// @Param status query string false "Filter statistics by status"
|
||||
// @Success 200 {object} models.AggregateData "Statistics data"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/retribusis/stats [get]
|
||||
// @Router /retribusis/stats [get]
|
||||
func (h *RetribusiHandler) GetRetribusiStats(c *gin.Context) {
|
||||
dbConn, err := h.db.GetDB("postgres_satudata")
|
||||
if err != nil {
|
||||
|
||||
@@ -13,25 +13,26 @@ type Qris struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
Status string `json:"status" db:"status"`
|
||||
Sort models.NullableInt32 `json:"sort,omitempty" db:"sort"`
|
||||
UserCreated sql.NullString `json:"user_created,omitempty" db:"user_created"`
|
||||
PosDevice sql.NullString `json:"posdevice,omitempty" db:"posdevice"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UserUpdated sql.NullString `json:"user_updated,omitempty" db:"user_updated"`
|
||||
InvoiceNumber sql.NullString `json:"invoice_number,omitempty" db:"invoice_number"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at,omitempty" db:"updated_at"`
|
||||
DisplayName sql.NullString `json:"display_name,omitempty" db:"display_name"`
|
||||
DisplayAmount float64 `json:"display_amount" db:"display_amount"`
|
||||
QrValue string `json:"qrvalue" db:"qrvalue"`
|
||||
IP string `json:"ip" db:"ip"`
|
||||
IPAddress string `json:"ip" db:"ip"`
|
||||
DisplayNoBill string `json:"display_nobill" db:"display_nobill"`
|
||||
}
|
||||
|
||||
// Custom JSON marshaling untuk Qris agar NULL values tidak muncul di response
|
||||
func (r Qris) MarshalJSON() ([]byte, error) {
|
||||
type Alias Qris
|
||||
aux := &struct {
|
||||
Sort *int `json:"sort,omitempty"`
|
||||
UserCreated *string `json:"user_created,omitempty"`
|
||||
UserUpdated *string `json:"user_updated,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
DisplayName *string `json:"display_name,omitempty"`
|
||||
Sort *int `json:"sort,omitempty"`
|
||||
PosDevice *string `json:"posdevice,omitempty"`
|
||||
InvoiceNumber *string `json:"invoice_number,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
DisplayName *string `json:"display_name,omitempty"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(&r),
|
||||
@@ -41,11 +42,11 @@ func (r Qris) MarshalJSON() ([]byte, error) {
|
||||
sort := int(r.Sort.Int32)
|
||||
aux.Sort = &sort
|
||||
}
|
||||
if r.UserCreated.Valid {
|
||||
aux.UserCreated = &r.UserCreated.String
|
||||
if r.PosDevice.Valid {
|
||||
aux.PosDevice = &r.PosDevice.String
|
||||
}
|
||||
if r.UserUpdated.Valid {
|
||||
aux.UserUpdated = &r.UserUpdated.String
|
||||
if r.InvoiceNumber.Valid {
|
||||
aux.InvoiceNumber = &r.InvoiceNumber.String
|
||||
}
|
||||
if r.UpdatedAt.Valid {
|
||||
aux.UpdatedAt = &r.UpdatedAt.Time
|
||||
@@ -70,6 +71,12 @@ type QrisGetByIDResponse struct {
|
||||
Data *Qris `json:"data"`
|
||||
}
|
||||
|
||||
// Response struct untuk GET by IP
|
||||
type QrisGetByIPResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *Qris `json:"data"`
|
||||
}
|
||||
|
||||
// Enhanced GET response dengan pagination dan aggregation
|
||||
type QrisGetResponse struct {
|
||||
Message string `json:"message"`
|
||||
|
||||
@@ -73,21 +73,35 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||
{
|
||||
qrisQrisGroup.GET("", qrisQrisHandler.GetQris)
|
||||
qrisQrisGroup.GET("/:id", qrisQrisHandler.GetQrisByID)
|
||||
qrisQrisGroup.GET("/ip/:ip", qrisQrisHandler.GetQrisByIP)
|
||||
qrisQrisGroup.GET("/stats", qrisQrisHandler.GetQrisStats)
|
||||
qrisQrisGroup.GET("/dynamic", qrisQrisHandler.GetSearchDynamic)
|
||||
qrisQrisGroup.GET("/search", qrisQrisHandler.SearchAdvanced)
|
||||
}
|
||||
|
||||
// // Retribusi endpoints
|
||||
// retribusiHandler := retribusiHandlers.NewRetribusiHandler()
|
||||
// retribusiGroup := v1.Group("/retribusi")
|
||||
// {
|
||||
// retribusiGroup.GET("", retribusiHandler.GetRetribusi)
|
||||
// retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic) // Route baru
|
||||
// retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced) // Route pencarian
|
||||
// retribusiGroup.GET("/:id", retribusiHandler.GetRetribusiByID)
|
||||
// retribusiGroup.POST("", retribusiHandler.CreateRetribusi)
|
||||
// retribusiGroup.PUT("/:id", retribusiHandler.UpdateRetribusi)
|
||||
// retribusiGroup.DELETE("/:id", retribusiHandler.DeleteRetribusi)
|
||||
// }
|
||||
// Search endpoints
|
||||
/*qrisSearchHandler := qrisSearchHandlers.NewSearchHandler()
|
||||
qrisSearchGroup := v1.Group("/qris/search")
|
||||
{
|
||||
//qrisSearchGroup.GET("", qrisSearchHandler.GetSearch)
|
||||
qrisSearchGroup.GET("/dynamic", qrisSearchHandler.GetSearchDynamic) // Route baru
|
||||
qrisSearchGroup.GET("/advanced", qrisSearchHandler.SearchAdvanced) // Route pencarian
|
||||
//qrisSearchGroup.GET("/:id", qrisSearchHandler.GetSearchByID)
|
||||
qrisSearchGroup.GET("/stats", qrisSearchHandler.GetSearchStats)
|
||||
}*/
|
||||
|
||||
// Retribusi endpoints
|
||||
retribusiHandler := retribusiHandlers.NewRetribusiHandler()
|
||||
retribusiGroup := v1.Group("/retribusi")
|
||||
{
|
||||
retribusiGroup.GET("", retribusiHandler.GetRetribusi)
|
||||
retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic) // Route baru
|
||||
retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced) // Route pencarian
|
||||
retribusiGroup.GET("/:id", retribusiHandler.GetRetribusiByID)
|
||||
retribusiGroup.POST("", retribusiHandler.CreateRetribusi)
|
||||
retribusiGroup.PUT("/:id", retribusiHandler.UpdateRetribusi)
|
||||
retribusiGroup.DELETE("/:id", retribusiHandler.DeleteRetribusi)
|
||||
}
|
||||
// =============================================================================
|
||||
// PROTECTED ROUTES (Authentication Required)
|
||||
// =============================================================================
|
||||
@@ -100,15 +114,15 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||
protected.GET("/auth/me", authHandler.Me)
|
||||
|
||||
// Retribusi endpoints (CRUD operations - should be protected)
|
||||
retribusiHandler := retribusiHandlers.NewRetribusiHandler()
|
||||
protectedRetribusi := protected.Group("/retribusi")
|
||||
{
|
||||
protectedRetribusi.GET("", retribusiHandler.GetRetribusi) // GET /api/v1/retribusi
|
||||
protectedRetribusi.GET("/:id", retribusiHandler.GetRetribusiByID) // GET /api/v1/retribusi/:id
|
||||
protectedRetribusi.POST("/", retribusiHandler.CreateRetribusi) // POST /api/v1/retribusi/
|
||||
protectedRetribusi.PUT("/:id", retribusiHandler.UpdateRetribusi) // PUT /api/v1/retribusi/:id
|
||||
protectedRetribusi.DELETE("/:id", retribusiHandler.DeleteRetribusi) // DELETE /api/v1/retribusi/:id
|
||||
}
|
||||
// retribusiHandler := retribusiHandlers.NewRetribusiHandler()
|
||||
// protectedRetribusi := protected.Group("/retribusi")
|
||||
// {
|
||||
// protectedRetribusi.GET("", retribusiHandler.GetRetribusi) // GET /api/v1/retribusi
|
||||
// protectedRetribusi.GET("/:id", retribusiHandler.GetRetribusiByID) // GET /api/v1/retribusi/:id
|
||||
// protectedRetribusi.POST("/", retribusiHandler.CreateRetribusi) // POST /api/v1/retribusi/
|
||||
// protectedRetribusi.PUT("/:id", retribusiHandler.UpdateRetribusi) // PUT /api/v1/retribusi/:id
|
||||
// protectedRetribusi.DELETE("/:id", retribusiHandler.DeleteRetribusi) // DELETE /api/v1/retribusi/:id
|
||||
// }
|
||||
|
||||
// // BPJS VClaim endpoints (require authentication)
|
||||
// // Peserta routes
|
||||
|
||||
Reference in New Issue
Block a user