Initial commit

This commit is contained in:
2025-11-17 15:04:27 +07:00
parent fb23922d31
commit a14562cfe0
14 changed files with 640 additions and 6673 deletions
+30 -30
View File
@@ -761,18 +761,18 @@ func (c *Config) Validate() error {
}
}
if c.Bpjs.BaseURL == "" {
log.Fatal("BPJS Base URL is required")
}
if c.Bpjs.ConsID == "" {
log.Fatal("BPJS Consumer ID is required")
}
if c.Bpjs.UserKey == "" {
log.Fatal("BPJS User Key is required")
}
if c.Bpjs.SecretKey == "" {
log.Fatal("BPJS Secret Key is required")
}
// if c.Bpjs.BaseURL == "" {
// log.Fatal("BPJS Base URL is required")
// }
// if c.Bpjs.ConsID == "" {
// log.Fatal("BPJS Consumer ID is required")
// }
// if c.Bpjs.UserKey == "" {
// log.Fatal("BPJS User Key is required")
// }
// if c.Bpjs.SecretKey == "" {
// log.Fatal("BPJS Secret Key is required")
// }
// Validate Keycloak configuration if enabled
if c.Keycloak.Enabled {
@@ -788,24 +788,24 @@ func (c *Config) Validate() error {
}
// Validate SatuSehat configuration
if c.SatuSehat.OrgID == "" {
log.Fatal("SatuSehat Organization ID is required")
}
if c.SatuSehat.FasyakesID == "" {
log.Fatal("SatuSehat Fasyankes ID is required")
}
if c.SatuSehat.ClientID == "" {
log.Fatal("SatuSehat Client ID is required")
}
if c.SatuSehat.ClientSecret == "" {
log.Fatal("SatuSehat Client Secret is required")
}
if c.SatuSehat.AuthURL == "" {
log.Fatal("SatuSehat Auth URL is required")
}
if c.SatuSehat.BaseURL == "" {
log.Fatal("SatuSehat Base URL is required")
}
// if c.SatuSehat.OrgID == "" {
// log.Fatal("SatuSehat Organization ID is required")
// }
// if c.SatuSehat.FasyakesID == "" {
// log.Fatal("SatuSehat Fasyankes ID is required")
// }
// if c.SatuSehat.ClientID == "" {
// log.Fatal("SatuSehat Client ID is required")
// }
// if c.SatuSehat.ClientSecret == "" {
// log.Fatal("SatuSehat Client Secret is required")
// }
// if c.SatuSehat.AuthURL == "" {
// log.Fatal("SatuSehat Auth URL is required")
// }
// if c.SatuSehat.BaseURL == "" {
// log.Fatal("SatuSehat Base URL is required")
// }
// Validate WebSocket configuration
if c.WebSocket.ReadTimeout <= 0 {
File diff suppressed because it is too large Load Diff
+134 -1
View File
@@ -3,6 +3,8 @@ package websocket
import (
"api-service/internal/config"
ws "api-service/internal/services/websocket"
"context"
"database/sql"
"fmt"
"net/http"
"time"
@@ -88,7 +90,7 @@ func (h *WebSocketHandler) TestWebSocketConnection(c *gin.Context) {
"user_id": "optional, for user identification",
"room": "optional, for room-based messaging",
},
"example": "ws://meninjar.dev.rssa.id:8070/api/v1/ws?client_id=test_client&room=test_room",
// "example": "ws://meninjar.dev.rssa.id:8070/api/v1/ws?client_id=test_client&room=test_room",
"timestamp": time.Now().Unix(),
})
}
@@ -134,3 +136,134 @@ func (h *WebSocketHandler) CleanupInactiveClients(c *gin.Context) {
"timestamp": time.Now().Unix(),
})
}
func (h *WebSocketHandler) BroadcastQris(c *gin.Context) {
var req struct {
Data map[string]interface{} `json:"data"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
dbConn, err := h.hub.GetDatabaseConnection("simrs_backup")
if err != nil || dbConn == nil {
c.JSON(500, gin.H{"error": "Database connection failed"})
return
}
ip, _ := req.Data["ip"].(string)
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
defer cancel()
posDevices, err := h.fetchPosDeviceByIP(ctx, dbConn, ip)
broadcaster := ws.NewBroadcaster(h.hub)
if err != nil || len(posDevices) == 0 {
broadcaster.BroadcastQris("qris_posdevice", map[string]interface{}{
"data": req.Data,
"posdevice": nil,
"message": "No posdevice found for this IP",
"timestamp": time.Now().Unix(),
})
c.JSON(404, gin.H{
"error": "No posdevice found for this IP",
"data": req.Data,
"timestamp": time.Now().Unix(),
})
return
}
broadcaster.BroadcastQris("qris_posdevice", map[string]interface{}{
"data": req.Data,
"posdevice": posDevices,
"timestamp": time.Now().Unix(),
})
c.JSON(200, gin.H{
"status": "broadcast sent",
"data": req.Data,
"posdevice": posDevices,
"timestamp": time.Now().Unix(),
})
}
func (h *WebSocketHandler) BroadcastCheck(c *gin.Context) {
var req struct {
Data map[string]interface{} `json:"data"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
dbConn, err := h.hub.GetDatabaseConnection("simrs_backup")
if err != nil || dbConn == nil {
c.JSON(500, gin.H{"error": "Database connection failed"})
return
}
ip, _ := req.Data["ip"].(string)
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
defer cancel()
posDevices, err := h.fetchPosDeviceByIP(ctx, dbConn, ip)
broadcaster := ws.NewBroadcaster(h.hub)
if err != nil || len(posDevices) == 0 {
broadcaster.BroadcastCheck("qris_check", map[string]interface{}{
"data": req.Data,
"posdevice": nil,
"message": "No posdevice found for this IP",
"timestamp": time.Now().Unix(),
})
c.JSON(404, gin.H{
"error": "No posdevice found for this IP",
"data": req.Data,
"timestamp": time.Now().Unix(),
})
return
}
broadcaster.BroadcastCheck("qris_check", map[string]interface{}{
"data": req.Data,
"posdevice": posDevices,
"timestamp": time.Now().Unix(),
})
c.JSON(200, gin.H{
"status": "broadcast sent",
"data": req.Data,
"posdevice": posDevices,
"timestamp": time.Now().Unix(),
})
}
type Qris struct {
PosDevice string `json:"posdevice"`
}
func (h *WebSocketHandler) fetchPosDeviceByIP(ctx context.Context, db *sql.DB, ip string) ([]string, error) {
query := `SELECT posdevice FROM m_deviceqris WHERE ip = $1 AND status = '1'`
rows, err := db.QueryContext(ctx, query, ip)
if err != nil {
return nil, err
}
defer rows.Close()
var posDevices []string
for rows.Next() {
var posDevice string
if err := rows.Scan(&posDevice); err != nil {
return nil, err
}
posDevices = append(posDevices, posDevice)
}
return posDevices, nil
}
-228
View File
@@ -1,228 +0,0 @@
package retribusi
import (
"api-service/internal/models"
"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 models.NullableInt32 `json:"sort,omitempty" db:"sort"`
UserCreated models.NullableString `json:"user_created,omitempty" db:"user_created"`
DateCreated models.NullableTime `json:"date_created,omitempty" db:"date_created"`
UserUpdated models.NullableString `json:"user_updated,omitempty" db:"user_updated"`
DateUpdated models.NullableTime `json:"date_updated,omitempty" db:"date_updated"`
Jenis models.NullableString `json:"jenis,omitempty" db:"Jenis"`
Pelayanan models.NullableString `json:"pelayanan,omitempty" db:"Pelayanan"`
Dinas models.NullableString `json:"dinas,omitempty" db:"Dinas"`
KelompokObyek models.NullableString `json:"kelompok_obyek,omitempty" db:"Kelompok_obyek"`
KodeTarif models.NullableString `json:"kode_tarif,omitempty" db:"Kode_tarif"`
Tarif models.NullableString `json:"tarif,omitempty" db:"Tarif"`
Satuan models.NullableString `json:"satuan,omitempty" db:"Satuan"`
TarifOvertime models.NullableString `json:"tarif_overtime,omitempty" db:"Tarif_overtime"`
SatuanOvertime models.NullableString `json:"satuan_overtime,omitempty" db:"Satuan_overtime"`
RekeningPokok models.NullableString `json:"rekening_pokok,omitempty" db:"Rekening_pokok"`
RekeningDenda models.NullableString `json:"rekening_denda,omitempty" db:"Rekening_denda"`
Uraian1 models.NullableString `json:"uraian_1,omitempty" db:"Uraian_1"`
Uraian2 models.NullableString `json:"uraian_2,omitempty" db:"Uraian_2"`
Uraian3 models.NullableString `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 NullableInt32 to pointer
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 models.MetaResponse `json:"meta"`
Summary *models.AggregateData `json:"summary,omitempty"`
}
// 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"`
}
+66 -48
View File
@@ -3,9 +3,7 @@ package v1
import (
"api-service/internal/config"
"api-service/internal/database"
authHandlers "api-service/internal/handlers/auth"
healthcheckHandlers "api-service/internal/handlers/healthcheck"
retribusiHandlers "api-service/internal/handlers/retribusi"
websocketHandlers "api-service/internal/handlers/websocket"
"api-service/internal/middleware"
services "api-service/internal/services/auth"
@@ -22,7 +20,7 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
router := gin.New()
// Initialize auth middleware configuration
middleware.InitializeAuth(cfg)
middleware.AuthJWTMiddleware()
// Add global middleware
router.Use(middleware.CORSConfig())
@@ -90,17 +88,17 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
// =============================================================================
// Authentication routes
authHandler := authHandlers.NewAuthHandler(authService)
tokenHandler := authHandlers.NewTokenHandler(authService)
// authHandler := authHandlers.NewAuthHandler(authService)
// tokenHandler := authHandlers.NewTokenHandler(authService)
// Basic auth routes
v1.POST("/auth/login", authHandler.Login)
v1.POST("/auth/register", authHandler.Register)
v1.POST("/auth/refresh", authHandler.RefreshToken)
// v1.POST("/auth/login", authHandler.Login)
// v1.POST("/auth/register", authHandler.Register)
// v1.POST("/auth/refresh", authHandler.RefreshToken)
// Token generation routes
v1.POST("/token/generate", tokenHandler.GenerateToken)
v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
// v1.POST("/token/generate", tokenHandler.GenerateToken)
// v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
// =============================================================================
// WEBSOCKET ROUTES
@@ -123,52 +121,72 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
v1.GET("/ws", websocketHandler.HandleWebSocket)
v1.GET("/ws/test", websocketHandler.TestWebSocketConnection)
v1.GET("/ws/stats", websocketHandler.GetWebSocketStats)
v1.POST("/ws/broadcast/qris", websocketHandler.BroadcastQris)
v1.POST("/ws/broadcast/check", websocketHandler.BroadcastCheck)
// Retribusi endpoints with WebSocket notifications
retribusiHandler := retribusiHandlers.NewRetribusiHandler()
retribusiGroup := v1.Group("/retribusi")
{
retribusiGroup.GET("", retribusiHandler.GetRetribusi)
retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic)
retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced)
retribusiGroup.GET("/id/:id", retribusiHandler.GetRetribusiByID)
// retribusiHandler := retribusiHandlers.NewRetribusiHandler()
// retribusiGroup := v1.Group("/retribusi")
// {
// retribusiGroup.GET("", retribusiHandler.GetRetribusi)
// retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic)
// retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced)
// retribusiGroup.GET("/id/:id", retribusiHandler.GetRetribusiByID)
// POST/PUT/DELETE with automatic WebSocket notifications
retribusiGroup.POST("", func(c *gin.Context) {
retribusiHandler.CreateRetribusi(c)
// // POST/PUT/DELETE with automatic WebSocket notifications
// retribusiGroup.POST("", func(c *gin.Context) {
// retribusiHandler.CreateRetribusi(c)
// Trigger WebSocket notification after successful creation
if c.Writer.Status() == 200 || c.Writer.Status() == 201 {
// Notify database change via WebSocket
// websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
// fmt.Sprintf(`{"action": "created", "timestamp": "%s"}`, time.Now().Format(time.RFC3339)))
}
})
// // Trigger WebSocket notification after successful creation
// if c.Writer.Status() == 200 || c.Writer.Status() == 201 {
// // Notify database change via WebSocket
// // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
// // fmt.Sprintf(`{"action": "created", "timestamp": "%s"}`, time.Now().Format(time.RFC3339)))
// }
// })
retribusiGroup.PUT("/id/:id", func(c *gin.Context) {
// id := c.Param("id")
retribusiHandler.UpdateRetribusi(c)
// retribusiGroup.PUT("/id/:id", func(c *gin.Context) {
// // id := c.Param("id")
// retribusiHandler.UpdateRetribusi(c)
// Trigger WebSocket notification after successful update
if c.Writer.Status() == 200 {
// Notify database change via WebSocket
// websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
// fmt.Sprintf(`{"action": "updated", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339)))
}
})
// // Trigger WebSocket notification after successful update
// if c.Writer.Status() == 200 {
// // Notify database change via WebSocket
// // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
// // fmt.Sprintf(`{"action": "updated", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339)))
// }
// })
retribusiGroup.DELETE("/id/:id", func(c *gin.Context) {
// id := c.Param("id")
retribusiHandler.DeleteRetribusi(c)
// retribusiGroup.DELETE("/id/:id", func(c *gin.Context) {
// // id := c.Param("id")
// retribusiHandler.DeleteRetribusi(c)
// Trigger WebSocket notification after successful deletion
if c.Writer.Status() == 200 {
// Notify database change via WebSocket
// websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
// fmt.Sprintf(`{"action": "deleted", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339)))
}
})
}
// // Trigger WebSocket notification after successful deletion
// if c.Writer.Status() == 200 {
// // Notify database change via WebSocket
// // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
// // fmt.Sprintf(`{"action": "deleted", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339)))
// }
// })
// }
// =============================================================================
// PROTECTED ROUTES (Authentication Required)
// =============================================================================
// Create protected group with configurable authentication
protected := v1.Group("/")
protected.Use(middleware.AuthJWTMiddleware()) // Use configurable authentication
// User profile (protected)
// protected.GET("/auth/me", authHandler.Me)
// Retribusi endpoints (CRUD operations - should be protected)
// protectedQris := protected.Group("/ws")
// {
// protectedQris.POST("/qris/broadcast", websocketHandler.BroadcastQris) // POST /api/v1/ws/
// protectedQris.POST("/check/broadcast", websocketHandler.BroadcastCheck) // POST /api/v1/ws/
// }
return router
}
@@ -1,6 +1,7 @@
package websocket
import (
"api-service/pkg/logger"
"time"
)
@@ -108,6 +109,64 @@ func (b *Broadcaster) BroadcastMessage(messageType string, data interface{}) {
}
}
// BroadcastQris godoc
// @Summary Broadcast a QRIS-related WebSocket message
// @Description Creates and broadcasts a WebSocket message with the specified type and data for QRIS operations
// @Tags WebSocket QRIS
// @Accept json
// @Produce json
// @Param messageType path string true "Type of the QRIS message to broadcast"
// @Param data body interface{} true "QRIS data payload for the message"
// @Success 200 {object} map[string]string "QRIS message successfully queued for broadcast"
// @Failure 500 {object} map[string]string "Failed to queue QRIS message (queue full)"
// @Router /api/v1/ws/broadcast/qris [post]
func (b *Broadcaster) BroadcastQris(messageType string, data interface{}) {
msg := NewWebSocketMessage(MessageType(messageType), data, "", "")
select {
case b.hub.messageQueue <- msg:
default:
// Antrian penuh, abaikan pesan
logger.Error("Message queue full, dropping message")
}
// Show posdevice if present
// if m, ok := data.(map[string]interface{}); ok {
// fmt.Println("BroadcastQris called with IP display: ", m["posdevice"])
// }
//tabel m_deviceqris
//kolom posdevice dari nama lokasi jadi ip display
//kolom ip dari ip simrs
}
// BroadcastCheck godoc
// @Summary Broadcast a WebSocket message
// @Description Creates and broadcasts a WebSocket message with the specified type and data
// @Tags WebSocket QRIS
// @Accept json
// @Produce json
// @Param messageType path string true "Type of the message to broadcast"
// @Param data body interface{} true "Data payload for the message"
// @Success 200 {object} map[string]string "Message successfully queued for broadcast"
// @Failure 500 {object} map[string]string "Failed to queue message (queue full)"
// @Router /api/v1/ws/broadcast/check [post]
func (b *Broadcaster) BroadcastCheck(messageType string, data interface{}) {
msg := NewWebSocketMessage(MessageType(messageType), data, "", "")
select {
case b.hub.messageQueue <- msg:
default:
// Antrian penuh, abaikan pesan
logger.Error("Message queue full, dropping message")
}
// Show posdevice if present
// if m, ok := data.(map[string]interface{}); ok {
// fmt.Println("BroadcastCheck called with IP display: ", m["posdevice"])
// }
}
// BroadcastToRoom mengirim pesan ke ruangan tertentu
func (b *Broadcaster) BroadcastToRoom(room string, messageType string, data interface{}) {
msg := NewWebSocketMessage(
+5 -5
View File
@@ -40,7 +40,7 @@ func (h *DatabaseHandler) handleDatabaseInsert(client *Client, message WebSocket
return nil
}
table, ok := data["table"].(string)
table, ok := data["m_deviceqris"].(string)
if !ok || table == "" {
client.sendErrorResponse("Invalid table name", "table is required")
return nil
@@ -55,7 +55,7 @@ func (h *DatabaseHandler) handleDatabaseInsert(client *Client, message WebSocket
// Perform actual database insert
if h.hub.dbService != nil {
// Get database connection
db, err := h.hub.GetDatabaseConnection("postgres_satudata")
db, err := h.hub.GetDatabaseConnection("simrs_backup")
if err != nil {
client.sendErrorResponse("Database connection error", err.Error())
return nil
@@ -110,14 +110,14 @@ func (h *DatabaseHandler) handleDatabaseQuery(client *Client, message WebSocketM
return nil
}
table, ok := data["table"].(string)
table, ok := data["m_deviceqris"].(string)
if !ok || table == "" {
client.sendErrorResponse("Invalid table name", "table is required")
return nil
}
// Execute query
results, err := h.hub.ExecuteDatabaseQuery("postgres_satudata", fmt.Sprintf("SELECT * FROM %s LIMIT 100", table))
results, err := h.hub.ExecuteDatabaseQuery("simrs_backup", fmt.Sprintf("SELECT * FROM %s LIMIT 100", table))
if err != nil {
client.sendErrorResponse("Database query error", err.Error())
return nil
@@ -142,7 +142,7 @@ func (h *DatabaseHandler) handleDatabaseCustomQuery(client *Client, message WebS
database, ok := data["database"].(string)
if !ok || database == "" {
database = "postgres_satudata"
database = "simrs_backup"
}
query, ok := data["query"].(string)
+3 -3
View File
@@ -64,7 +64,7 @@ type ActivityLog struct {
// DatabaseService mendefinisikan interface untuk layanan database
type DatabaseService interface {
Health() map[string]interface{}
ListDBs() []string
//ListDBs() []string
ListenForChanges(ctx context.Context, dbName string, channels []string, callback func(channel, payload string)) error
NotifyChange(dbName, channel, payload string) error
GetDB(name string) (*sql.DB, error)
@@ -345,9 +345,9 @@ func (h *Hub) GetStats() map[string]interface{} {
// setupDatabaseListeners sets up database change listeners for real-time updates
func (h *Hub) setupDatabaseListeners() {
// Listen for changes on retribusi table
channels := []string{"retribusi_changes", "data_changes"}
channels := []string{"data_changes"}
err := h.dbService.ListenForChanges(h.ctx, "postgres_satudata", channels, func(channel, payload string) {
err := h.dbService.ListenForChanges(h.ctx, "simrs_backup", channels, func(channel, payload string) {
h.handleDatabaseChange(channel, payload)
})