270 lines
7.2 KiB
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
|
|
}
|