Files
websocket-qris/internal/handlers/websocket/websocket.go

270 lines
7.2 KiB
Go

package websocket
import (
"api-service/internal/config"
ws "api-service/internal/services/websocket"
"context"
"database/sql"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
// WebSocketHandler menangani koneksi WebSocket
type WebSocketHandler struct {
hub *ws.Hub
config config.WebSocketConfig
upgrader websocket.Upgrader
monitoringManager *ws.MonitoringManager
}
// NewWebSocketHandler membuat handler WebSocket baru
func NewWebSocketHandler(hub *ws.Hub, config config.WebSocketConfig) *WebSocketHandler {
return &WebSocketHandler{
hub: hub,
config: config,
monitoringManager: ws.NewMonitoringManager(hub),
upgrader: websocket.Upgrader{
ReadBufferSize: config.ReadBufferSize,
WriteBufferSize: config.WriteBufferSize,
CheckOrigin: func(r *http.Request) bool {
// Dalam production, implementasikan validasi origin yang proper
return true
},
},
}
}
// HandleWebSocket menangani upgrade koneksi ke WebSocket
func (h *WebSocketHandler) HandleWebSocket(c *gin.Context) {
// Ambil parameter dari query atau header
clientID := c.Query("client_id")
if clientID == "" {
clientID = fmt.Sprintf("client_%d", time.Now().UnixNano())
}
staticID := c.Query("static_id")
userID := c.Query("user_id")
room := c.Query("room")
ipAddress := c.ClientIP()
// Upgrade koneksi HTTP ke WebSocket
conn, err := h.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Failed to upgrade connection",
"details": err.Error(),
})
return
}
// Buat klien WebSocket baru
client := ws.NewClient(clientID, staticID, userID, room, ipAddress, conn, h.hub, h.config)
// Daftarkan klien ke hub
h.hub.RegisterChannel() <- client
// Jalankan goroutine untuk membaca dan menulis
go client.ReadPump()
go client.WritePump()
}
// GetWebSocketStats mengembalikan statistik WebSocket
func (h *WebSocketHandler) GetWebSocketStats(c *gin.Context) {
stats := h.hub.GetStats()
c.JSON(http.StatusOK, stats)
}
// TestWebSocketConnection endpoint untuk test koneksi WebSocket
func (h *WebSocketHandler) TestWebSocketConnection(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "WebSocket service is running",
"status": "active",
"endpoint": "/api/v1/ws",
"parameters": gin.H{
"client_id": "optional, auto-generated if not provided",
"static_id": "optional, for persistent client identification",
"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",
"timestamp": time.Now().Unix(),
})
}
// GetDetailedStats mengembalikan statistik detail WebSocket
func (h *WebSocketHandler) GetDetailedStats(c *gin.Context) {
detailedStats := h.monitoringManager.GetDetailedStats()
c.JSON(http.StatusOK, gin.H{
"admin_stats": detailedStats,
"timestamp": time.Now().Unix(),
})
}
// DisconnectClient memutuskan koneksi klien tertentu
func (h *WebSocketHandler) DisconnectClient(c *gin.Context) {
clientID := c.Param("clientId")
success := h.monitoringManager.DisconnectClient(clientID)
c.JSON(http.StatusOK, gin.H{
"status": "force disconnect attempted",
"client_id": clientID,
"success": success,
"timestamp": time.Now().Unix(),
})
}
// CleanupInactiveClients membersihkan klien tidak aktif
func (h *WebSocketHandler) CleanupInactiveClients(c *gin.Context) {
var req struct {
InactiveMinutes int `json:"inactive_minutes"`
Force bool `json:"force"`
}
if err := c.ShouldBindJSON(&req); err != nil {
req.InactiveMinutes = 10
req.Force = false
}
cleanedCount := h.monitoringManager.CleanupInactiveClients(time.Duration(req.InactiveMinutes) * time.Minute)
c.JSON(http.StatusOK, gin.H{
"status": "admin cleanup completed",
"cleaned_clients": cleanedCount,
"inactive_minutes": req.InactiveMinutes,
"force": req.Force,
"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_prod")
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_prod")
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
}