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 postdevice 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 }