Files
antrean-anjungan/internal/handlers/kiosk/listkiosk.go
Annisa Rachmadiyanti f10689c606
Some checks failed
Go-test / build (push) Has been cancelled
Patient
2025-12-01 10:45:50 +07:00

887 lines
26 KiB
Go

package handlers
import (
"api-service/internal/config"
"api-service/internal/database"
models "api-service/internal/models"
"api-service/internal/models/kiosk"
"api-service/internal/utils/validation"
"context"
"database/sql"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
var (
listkioskdb database.Service
listkioskonce sync.Once
listkioskvalidate *validator.Validate
)
// Initialize the database connection and validator
func init() {
listkioskonce.Do(func() {
listkioskdb = database.New(config.LoadConfig())
listkioskvalidate = validator.New()
listkioskvalidate.RegisterValidation("listkiosk_status", validateListkioskStatus)
if listkioskdb == nil {
log.Fatal("Failed to initialize database connection")
}
})
}
// Custom validation for listkiosk status
func validateListkioskStatus(fl validator.FieldLevel) bool {
return models.IsValidStatus(fl.Field().String())
}
// ListkioskHandler handles listkiosk services
type ListkioskHandler struct {
db database.Service
}
// NewListkioskHandler creates a new ListkioskHandler
func NewListkioskHandler() *ListkioskHandler {
return &ListkioskHandler{
db: listkioskdb,
}
}
// GetListkiosk godoc
// @Summary Get listkiosk with pagination and optional aggregation
// @Description Returns a paginated list of listkiosks with optional summary statistics
// @Tags Listkiosk
// @Accept json
// @Produce json
// @Param limit query int false "Limit (max 100)" default(10)
// @Param offset query int false "Offset" default(0)
// @Param include_summary query bool false "Include aggregation summary" default(false)
// @Param status query string false "Filter by status"
// @Param search query string false "Search in multiple fields"
// @Success 200 {object} kiosk.ListkioskGetResponse "Success response"
// @Failure 400 {object} models.ErrorResponse "Bad request"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /kiosk/listkiosk [get]
func (h *ListkioskHandler) GetListkiosk(c *gin.Context) {
// Parse pagination parameters
limit, offset, err := h.parsePaginationParams(c)
if err != nil {
h.respondError(c, "Invalid pagination parameters", err, http.StatusBadRequest)
return
}
// Parse filter parameters
filter := h.parseFilterParams(c)
includeAggregation := c.Query("include_summary") == "true"
// Get database connection
dbConn, err := h.db.GetDB("db_antrean")
if err != nil {
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
return
}
// Create context with timeout
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
// Execute concurrent operations
var (
items []kiosk.Listkiosk
total int
aggregateData *models.AggregateData
wg sync.WaitGroup
errChan = make(chan error, 3)
mu sync.Mutex
)
// Fetch total count
wg.Add(1)
go func() {
defer wg.Done()
if err := h.getTotalCount(ctx, dbConn, filter, &total); err != nil {
mu.Lock()
errChan <- fmt.Errorf("failed to get total count: %w", err)
mu.Unlock()
}
}()
// Fetch main data
wg.Add(1)
go func() {
defer wg.Done()
result, err := h.fetchListkiosks(ctx, dbConn, filter, limit, offset)
mu.Lock()
if err != nil {
errChan <- fmt.Errorf("failed to fetch data: %w", err)
} else {
items = result
}
mu.Unlock()
}()
// Fetch aggregation data if requested
if includeAggregation {
wg.Add(1)
go func() {
defer wg.Done()
result, err := h.getAggregateData(ctx, dbConn, filter)
mu.Lock()
if err != nil {
errChan <- fmt.Errorf("failed to get aggregate data: %w", err)
} else {
aggregateData = result
}
mu.Unlock()
}()
}
// Wait for all goroutines
wg.Wait()
close(errChan)
// Check for errors
for err := range errChan {
if err != nil {
h.logAndRespondError(c, "Data processing failed", err, http.StatusInternalServerError)
return
}
}
// Build response
meta := h.calculateMeta(limit, offset, total)
response := kiosk.ListkioskGetResponse{
Message: "Data kiosk berhasil diambil",
Data: items,
Meta: meta,
}
if includeAggregation && aggregateData != nil {
response.Summary = aggregateData
}
c.JSON(http.StatusOK, response)
}
// GetListkioskByID godoc
// @Summary Get Listkiosk by ID
// @Description Returns a single listkiosk by ID
// @Tags Listkiosk
// @Accept json
// @Produce json
// @Param id path string true "Listkiosk ID (UUID)"
// @Success 200 {object} kiosk.ListkioskGetByIDResponse "Success response"
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
// @Failure 404 {object} models.ErrorResponse "Listkiosk not found"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/listkiosk/{id} [get]
// func (h *ListkioskHandler) GetListkioskByID(c *gin.Context) {
// id := c.Param("id")
// // Validate UUID format
// if _, err := uuid.Parse(id); err != nil {
// h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
// return
// }
// dbConn, err := h.db.GetDB("postgres_satudata")
// if err != nil {
// h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
// return
// }
// ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
// defer cancel()
// item, err := h.getListkioskByID(ctx, dbConn, id)
// if err != nil {
// if err == sql.ErrNoRows {
// h.respondError(c, "Listkiosk not found", err, http.StatusNotFound)
// } else {
// h.logAndRespondError(c, "Failed to get listkiosk", err, http.StatusInternalServerError)
// }
// return
// }
// response := kiosk.ListkioskGetByIDResponse{
// Message: "kiosk details retrieved successfully",
// Data: item,
// }
// c.JSON(http.StatusOK, response)
// }
// CreateListkiosk godoc
// @Summary Create listkiosk
// @Description Creates a new listkiosk record
// @Tags Listkiosk
// @Accept json
// @Produce json
// @Param request body kiosk.ListkioskCreateRequest true "Listkiosk creation request"
// @Success 201 {object} kiosk.ListkioskCreateResponse "Listkiosk created successfully"
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /kiosk [post]
func (h *ListkioskHandler) CreateKiosk(c *gin.Context) {
var req kiosk.ListkioskCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
return
}
// Validate request
if err := listkioskvalidate.Struct(&req); err != nil {
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
return
}
dbConn, err := h.db.GetDB("db_antrean")
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()
// Validate duplicate and daily submission
if err := h.validateListkioskSubmission(ctx, dbConn, &req); err != nil {
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
return
}
item, err := h.createKiosk(ctx, dbConn, &req)
if err != nil {
h.logAndRespondError(c, "Failed to create listkiosk", err, http.StatusInternalServerError)
return
}
response := kiosk.ListkioskCreateResponse{
Message: "Listkiosk berhasil dibuat",
Data: item,
}
c.JSON(http.StatusCreated, response)
}
// UpdateListkiosk godoc
// @Summary Update listkiosk
// @Description Updates an existing listkiosk record
// @Tags Listkiosk
// @Accept json
// @Produce json
// @Param id path int true "Kiosk ID (integer)"
// @Param request body kiosk.ListkioskUpdateRequest true "Listkiosk update request"
// @Success 200 {object} kiosk.ListkioskUpdateResponse "Listkiosk updated successfully"
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
// @Failure 404 {object} models.ErrorResponse "Listkiosk not found"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /kiosk/{id} [put]
func (h *ListkioskHandler) UpdateKiosk(c *gin.Context) {
id := c.Param("id")
// Validate UUID format
// if _, err := uuid.Parse(id); err != nil {
// h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
// return
// }
// Validate ID is integer
if _, err := strconv.Atoi(id); err != nil {
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
return
}
var req kiosk.ListkioskUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
return
}
// Set ID from path parameter
// req.ID = id
idInt, _ := strconv.Atoi(id)
req.ID = idInt
// Validate request
if err := listkioskvalidate.Struct(&req); err != nil {
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
return
}
dbConn, err := h.db.GetDB("db_antrean")
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.updateKiosk(ctx, dbConn, &req)
if err != nil {
if err == sql.ErrNoRows {
h.respondError(c, "Listkiosk not found", err, http.StatusNotFound)
} else {
h.logAndRespondError(c, "Failed to update listkiosk", err, http.StatusInternalServerError)
}
return
}
response := kiosk.ListkioskUpdateResponse{
Message: "Listkiosk berhasil diperbarui",
Data: item,
}
c.JSON(http.StatusOK, response)
}
// DeleteListkiosk godoc
// @Summary Delete listkiosk
// @Description Soft deletes a listkiosk by setting status to 'deleted'
// @Tags Listkiosk
// @Accept json
// @Produce json
// @Param id path int true "Kiosk ID (integer)"
// @Success 200 {object} kiosk.ListkioskDeleteResponse "Listkiosk deleted successfully"
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
// @Failure 404 {object} models.ErrorResponse "Listkiosk not found"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /kiosk/{id} [delete]
func (h *ListkioskHandler) DeleteKiosk(c *gin.Context) {
id := c.Param("id")
// Validate ID is integer
if _, err := strconv.Atoi(id); err != nil {
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
return
}
dbConn, err := h.db.GetDB("db_antrean")
if err != nil {
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel()
err = h.deleteKiosk(ctx, dbConn, id)
if err != nil {
if err == sql.ErrNoRows {
h.respondError(c, "Listkiosk not found", err, http.StatusNotFound)
} else {
h.logAndRespondError(c, "Failed to delete listkiosk", err, http.StatusInternalServerError)
}
return
}
response := kiosk.ListkioskDeleteResponse{
Message: "Listkiosk berhasil dihapus",
ID: id,
}
c.JSON(http.StatusOK, response)
}
// GetListkioskStats godoc
// @Summary Get listkiosk statistics
// @Description Returns comprehensive statistics about listkiosk data
// @Tags Listkiosk
// @Accept json
// @Produce json
// @Param status query string false "Filter statistics by status"
// @Success 200 {object} models.AggregateData "Statistics data"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/listkiosks/stats [get]
func (h *ListkioskHandler) GetKioskStats(c *gin.Context) {
dbConn, err := h.db.GetDB("db_antrean")
if err != nil {
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel()
filter := h.parseFilterParams(c)
aggregateData, err := h.getAggregateData(ctx, dbConn, filter)
if err != nil {
h.logAndRespondError(c, "Failed to get statistics", err, http.StatusInternalServerError)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Statistik listkiosk berhasil diambil",
"data": aggregateData,
})
}
// Database operations
// func (h *ListkioskHandler) getListkioskByID(ctx context.Context, dbConn *sql.DB, id string) (*kiosk.Listkiosk, error) {
// query := "SELECT id, status, sort, user_created, date_created, user_updated, date_updated, name FROM data_kiosk_listkiosk WHERE id = $1 AND status != 'deleted'"
// row := dbConn.QueryRowContext(ctx, query, id)
// var item kiosk.Listkiosk
// err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name)
// if err != nil {
// return nil, err
// }
// return &item, nil
// }
func (h *ListkioskHandler) createKiosk(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskCreateRequest) (*kiosk.Listkiosk, error) {
// id := uuid.New().String()
// now := time.Now()
query := `INSERT INTO master.ms_kiosk
(name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location`
// ensure optional UUID-like string is passed as NULL when empty
var fkLocation interface{}
if strings.TrimSpace(req.FKSdLocationID) == "" {
fkLocation = nil
} else {
fkLocation = req.FKSdLocationID
}
row := dbConn.QueryRowContext(ctx, query, req.Name, req.Icon, req.Url, req.Active, req.FKRefHealthcareTypeID, req.FKRefServiceTypeID, fkLocation, req.DsSdLocation)
var item kiosk.Listkiosk
err := row.Scan(&item.ID, &item.Name, &item.Icon, &item.Url, &item.Active, &item.FKRefHealthcareTypeID, &item.FKRefServiceTypeID, &item.FKSdLocationID, &item.DsSdLocation)
if err != nil {
return nil, fmt.Errorf("failed to create kiosk: %w", err)
}
return &item, nil
}
func (h *ListkioskHandler) updateKiosk(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskUpdateRequest) (*kiosk.Listkiosk, error) {
// now := time.Now()
query := `UPDATE master.ms_kiosk
SET name = $2, icon = $3, url = $4, active = $5, fk_ref_healthcare_type_id = $6, fk_ref_service_type_id = $7, fk_sd_location_id = $8, ds_sd_location = $9
WHERE id = $1
RETURNING id, name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location`
// ensure optional UUID-like string is passed as NULL when empty
var fkLocation interface{}
if strings.TrimSpace(req.FKSdLocationID) == "" {
fkLocation = nil
} else {
fkLocation = req.FKSdLocationID
}
row := dbConn.QueryRowContext(ctx, query, req.ID, req.Name, req.Icon, req.Url, req.Active, req.FKRefHealthcareTypeID, req.FKRefServiceTypeID, fkLocation, req.DsSdLocation)
var item kiosk.Listkiosk
err := row.Scan(&item.ID, &item.Name, &item.Icon, &item.Url, &item.Active, &item.FKRefHealthcareTypeID, &item.FKRefServiceTypeID, &item.FKSdLocationID, &item.DsSdLocation)
if err != nil {
return nil, fmt.Errorf("failed to update kiosk: %w", err)
}
return &item, nil
}
func (h *ListkioskHandler) deleteKiosk(ctx context.Context, dbConn *sql.DB, id string) error {
// now := time.Now()
query := `UPDATE master.ms_kiosk SET active = false
WHERE id = $1`
result, err := dbConn.ExecContext(ctx, query, id)
if err != nil {
return fmt.Errorf("failed to delete kiosk: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get affected rows: %w", err)
}
if rowsAffected == 0 {
return sql.ErrNoRows
}
return nil
}
func (h *ListkioskHandler) fetchListkiosks(ctx context.Context, dbConn *sql.DB, filter kiosk.ListkioskFilter, limit, offset int) ([]kiosk.Listkiosk, error) {
whereClause, args := h.buildWhereClause(filter)
query := fmt.Sprintf(`SELECT id, name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location
FROM master.ms_kiosk
WHERE %s 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 listkiosks query failed: %w", err)
}
defer rows.Close()
items := make([]kiosk.Listkiosk, 0, limit)
for rows.Next() {
item, err := h.scanListkiosk(rows)
if err != nil {
return nil, fmt.Errorf("scan Listkiosk failed: %w", err)
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("rows iteration error: %w", err)
}
log.Printf("Successfully fetched %d listkiosks with filters applied", len(items))
return items, nil
}
// Optimized scanning function
func (h *ListkioskHandler) scanListkiosk(rows *sql.Rows) (kiosk.Listkiosk, error) {
var item kiosk.Listkiosk
// Scan into individual fields to handle nullable types properly
err := rows.Scan(
&item.ID,
&item.Name,
&item.Icon,
&item.Url,
&item.Active,
&item.FKRefHealthcareTypeID,
&item.FKRefServiceTypeID,
&item.FKSdLocationID,
&item.DsSdLocation,
)
return item, err
}
func (h *ListkioskHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter kiosk.ListkioskFilter, total *int) error {
whereClause, args := h.buildWhereClause(filter)
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM master.ms_kiosk 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
}
// Get comprehensive aggregate data dengan filter support
func (h *ListkioskHandler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter kiosk.ListkioskFilter) (*models.AggregateData, error) {
aggregate := &models.AggregateData{
ByStatus: make(map[string]int),
}
// Build where clause untuk filter
whereClause, args := h.buildWhereClause(filter)
// Use concurrent execution untuk performance
var wg sync.WaitGroup
var mu sync.Mutex
errChan := make(chan error, 4)
// 1. Count by status
wg.Add(1)
go func() {
defer wg.Done()
statusQuery := fmt.Sprintf("SELECT status, COUNT(*) FROM master.ms_kiosk WHERE %s GROUP BY active ORDER BY active", whereClause)
rows, err := dbConn.QueryContext(ctx, statusQuery, args...)
if err != nil {
errChan <- fmt.Errorf("status query failed: %w", err)
return
}
defer rows.Close()
mu.Lock()
for rows.Next() {
var status string
var count int
if err := rows.Scan(&status, &count); err != nil {
mu.Unlock()
errChan <- fmt.Errorf("status scan failed: %w", err)
return
}
aggregate.ByStatus[status] = count
switch status {
case "active":
aggregate.TotalActive = count
case "draft":
aggregate.TotalDraft = count
case "inactive":
aggregate.TotalInactive = count
}
}
mu.Unlock()
if err := rows.Err(); err != nil {
errChan <- fmt.Errorf("status iteration error: %w", err)
}
}()
// 2. Get last updated time dan today statistics
wg.Add(1)
go func() {
defer wg.Done()
// Last updated
lastUpdatedQuery := fmt.Sprintf("SELECT MAX(date_updated) FROM master.ms_kiosk WHERE %s AND date_updated IS NOT NULL", whereClause)
var lastUpdated sql.NullTime
if err := dbConn.QueryRowContext(ctx, lastUpdatedQuery, args...).Scan(&lastUpdated); err != nil {
errChan <- fmt.Errorf("last updated query failed: %w", err)
return
}
// Today statistics
today := time.Now().Format("2006-01-02")
todayStatsQuery := fmt.Sprintf(`
SELECT
SUM(CASE WHEN DATE(date_created) = $%d THEN 1 ELSE 0 END) as created_today,
SUM(CASE WHEN DATE(date_updated) = $%d AND DATE(date_created) != $%d THEN 1 ELSE 0 END) as updated_today
FROM master.ms_kiosk
WHERE %s`, len(args)+1, len(args)+1, len(args)+1, whereClause)
todayArgs := append(args, today)
var createdToday, updatedToday int
if err := dbConn.QueryRowContext(ctx, todayStatsQuery, todayArgs...).Scan(&createdToday, &updatedToday); err != nil {
errChan <- fmt.Errorf("today stats query failed: %w", err)
return
}
mu.Lock()
if lastUpdated.Valid {
aggregate.LastUpdated = &lastUpdated.Time
}
aggregate.CreatedToday = createdToday
aggregate.UpdatedToday = updatedToday
mu.Unlock()
}()
// Wait for all goroutines
wg.Wait()
close(errChan)
// Check for errors
for err := range errChan {
if err != nil {
return nil, err
}
}
return aggregate, nil
}
// Enhanced error handling
func (h *ListkioskHandler) logAndRespondError(c *gin.Context, message string, err error, statusCode int) {
log.Printf("[ERROR] %s: %v", message, err)
h.respondError(c, message, err, statusCode)
}
func (h *ListkioskHandler) respondError(c *gin.Context, message string, err error, statusCode int) {
errorMessage := message
if gin.Mode() == gin.ReleaseMode {
errorMessage = "Internal server error"
}
c.JSON(statusCode, models.ErrorResponse{
Error: errorMessage,
Code: statusCode,
Message: err.Error(),
Timestamp: time.Now(),
})
}
// Parse pagination parameters dengan validation yang lebih ketat
func (h *ListkioskHandler) parsePaginationParams(c *gin.Context) (int, int, error) {
limit := 10 // Default limit
offset := 0 // Default offset
if limitStr := c.Query("limit"); limitStr != "" {
parsedLimit, err := strconv.Atoi(limitStr)
if err != nil {
return 0, 0, fmt.Errorf("invalid limit parameter: %s", limitStr)
}
if parsedLimit <= 0 {
return 0, 0, fmt.Errorf("limit must be greater than 0")
}
if parsedLimit > 100 {
return 0, 0, fmt.Errorf("limit cannot exceed 100")
}
limit = parsedLimit
}
if offsetStr := c.Query("offset"); offsetStr != "" {
parsedOffset, err := strconv.Atoi(offsetStr)
if err != nil {
return 0, 0, fmt.Errorf("invalid offset parameter: %s", offsetStr)
}
if parsedOffset < 0 {
return 0, 0, fmt.Errorf("offset cannot be negative")
}
offset = parsedOffset
}
log.Printf("Pagination - Limit: %d, Offset: %d", limit, offset)
return limit, offset, nil
}
func (h *ListkioskHandler) parseFilterParams(c *gin.Context) kiosk.ListkioskFilter {
filter := kiosk.ListkioskFilter{}
if status := c.Query("active"); status != "" {
if models.IsValidStatus(status) {
filter.Status = &status
}
}
if search := c.Query("search"); search != "" {
filter.Search = &search
}
// Parse date filters
if dateFromStr := c.Query("date_from"); dateFromStr != "" {
if dateFrom, err := time.Parse("2006-01-02", dateFromStr); err == nil {
filter.DateFrom = &dateFrom
}
}
if dateToStr := c.Query("date_to"); dateToStr != "" {
if dateTo, err := time.Parse("2006-01-02", dateToStr); err == nil {
filter.DateTo = &dateTo
}
}
return filter
}
// Build WHERE clause dengan filter parameters
func (h *ListkioskHandler) buildWhereClause(filter kiosk.ListkioskFilter) (string, []interface{}) {
conditions := []string{"1=1"}
args := []interface{}{}
paramCount := 1
if filter.Status != nil {
conditions = append(conditions, fmt.Sprintf("active = $%d", paramCount))
args = append(args, *filter.Status)
paramCount++
}
if filter.Search != nil {
searchCondition := fmt.Sprintf("name ILIKE $%d", paramCount)
conditions = append(conditions, searchCondition)
searchTerm := "%" + *filter.Search + "%"
args = append(args, searchTerm)
paramCount++
}
// if filter.DateFrom != nil {
// conditions = append(conditions, fmt.Sprintf("date_created >= $%d", paramCount))
// args = append(args, *filter.DateFrom)
// paramCount++
// }
// if filter.DateTo != nil {
// conditions = append(conditions, fmt.Sprintf("date_created <= $%d", paramCount))
// args = append(args, filter.DateTo.Add(24*time.Hour-time.Nanosecond))
// paramCount++
// }
return strings.Join(conditions, " AND "), args
}
func (h *ListkioskHandler) calculateMeta(limit, offset, total int) models.MetaResponse {
totalPages := 0
currentPage := 1
if limit > 0 {
totalPages = (total + limit - 1) / limit // Ceiling division
currentPage = (offset / limit) + 1
}
return models.MetaResponse{
Limit: limit,
Offset: offset,
Total: total,
TotalPages: totalPages,
CurrentPage: currentPage,
HasNext: offset+limit < total,
HasPrev: offset > 0,
}
}
// validateListkioskSubmission performs validation for duplicate entries and daily submission limits
func (h *ListkioskHandler) validateListkioskSubmission(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskCreateRequest) error {
// Import the validation utility
validator := validation.NewDuplicateValidator(dbConn)
// Use default configuration
config := validation.ValidationConfig{
TableName: "master.ms_kiosk",
IDColumn: "id",
// StatusColumn: "active",
// DateColumn: "date_created",
ActiveStatuses: []string{"active"},
AdditionalFields: map[string]interface{}{
"name": req.Name,
},
}
// Prepare fields for validation
fields := map[string]interface{}{
"name": req.Name,
"active": true,
}
err := validator.ValidateDuplicateWithCustomFields(ctx, config, fields)
if err != nil {
return fmt.Errorf("validation failed: %w", err)
}
return nil
}
// Example usage of the validation utility with custom configuration
// func (h *ListkioskHandler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskCreateRequest) error {
// // Create validator instance
// validator := validation.NewDuplicateValidator(dbConn)
// // Use custom configuration
// config := validation.ValidationConfig{
// TableName: "master.ms_kiosk",
// IDColumn: "id",
// StatusColumn: "active",
// DateColumn: "date_created",
// ActiveStatuses: []string{"true", "false"},
// AdditionalFields: map[string]interface{}{
// "name": req.Name,
// },
// }
// // Validate with custom fields
// fields := map[string]interface{}{
// "name": *req.Name,
// }
// err := validator.ValidateDuplicateWithCustomFields(ctx, config, fields)
// if err != nil {
// return fmt.Errorf("custom validation failed: %w", err)
// }
// return nil
// }
// GetLastSubmissionTime example
func (h *ListkioskHandler) getLastSubmissionTimeExample(ctx context.Context, dbConn *sql.DB, identifier string) (*time.Time, error) {
validator := validation.NewDuplicateValidator(dbConn)
return validator.GetLastSubmissionTime(ctx, "master.ms_kiosk", "id", "date_created", identifier)
}