first commit
This commit is contained in:
774
internal/routes/v1/routes.go
Normal file
774
internal/routes/v1/routes.go
Normal file
@@ -0,0 +1,774 @@
|
||||
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"
|
||||
"api-service/internal/handlers/websocket"
|
||||
websocketHandlers "api-service/internal/handlers/websocket"
|
||||
"api-service/internal/middleware"
|
||||
services "api-service/internal/services/auth"
|
||||
"api-service/pkg/logger"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||
router := gin.New()
|
||||
|
||||
// Initialize auth middleware configuration
|
||||
middleware.InitializeAuth(cfg)
|
||||
|
||||
// Add global middleware
|
||||
router.Use(middleware.CORSConfig())
|
||||
router.Use(middleware.ErrorHandler())
|
||||
router.Use(logger.RequestLoggerMiddleware(logger.Default()))
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
// Initialize services with error handling
|
||||
authService := services.NewAuthService(cfg)
|
||||
if authService == nil {
|
||||
logger.Fatal("Failed to initialize auth service")
|
||||
}
|
||||
|
||||
// Initialize database service
|
||||
dbService := database.New(cfg)
|
||||
|
||||
// Initialize WebSocket handler with enhanced features
|
||||
websocketHandler := websocketHandlers.NewWebSocketHandler(cfg, dbService)
|
||||
|
||||
// =============================================================================
|
||||
// HEALTH CHECK & SYSTEM ROUTES
|
||||
// =============================================================================
|
||||
|
||||
healthCheckHandler := healthcheckHandlers.NewHealthCheckHandler(dbService)
|
||||
sistem := router.Group("/api/sistem")
|
||||
{
|
||||
sistem.GET("/health", healthCheckHandler.CheckHealth)
|
||||
sistem.GET("/databases", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"databases": dbService.ListDBs(),
|
||||
"health": dbService.Health(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
sistem.GET("/info", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"service": "API Service v1.0.0",
|
||||
"websocket_active": true,
|
||||
"connected_clients": websocketHandler.GetConnectedClients(),
|
||||
"databases": dbService.ListDBs(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SWAGGER DOCUMENTATION
|
||||
// =============================================================================
|
||||
|
||||
router.GET("/swagger/*any", ginSwagger.WrapHandler(
|
||||
swaggerFiles.Handler,
|
||||
ginSwagger.DefaultModelsExpandDepth(-1),
|
||||
ginSwagger.DeepLinking(true),
|
||||
))
|
||||
|
||||
// =============================================================================
|
||||
// WEBSOCKET TEST CLIENT
|
||||
// =============================================================================
|
||||
|
||||
// router.GET("/websocket-test", func(c *gin.Context) {
|
||||
// c.Header("Content-Type", "text/html")
|
||||
// c.String(http.StatusOK, getWebSocketTestHTML())
|
||||
// })
|
||||
|
||||
// =============================================================================
|
||||
// API v1 GROUP
|
||||
// =============================================================================
|
||||
|
||||
v1 := router.Group("/api/v1")
|
||||
|
||||
// =============================================================================
|
||||
// PUBLIC ROUTES (No Authentication Required)
|
||||
// =============================================================================
|
||||
|
||||
// Authentication routes
|
||||
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)
|
||||
|
||||
// Token generation routes
|
||||
v1.POST("/token/generate", tokenHandler.GenerateToken)
|
||||
v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
|
||||
|
||||
// =============================================================================
|
||||
// WEBSOCKET ROUTES
|
||||
// =============================================================================
|
||||
|
||||
// Main WebSocket endpoint with enhanced features
|
||||
v1.GET("/ws", websocketHandler.HandleWebSocket)
|
||||
|
||||
// WebSocket management API
|
||||
wsAPI := router.Group("/api/websocket")
|
||||
{
|
||||
// =============================================================================
|
||||
// BASIC BROADCASTING
|
||||
// =============================================================================
|
||||
|
||||
wsAPI.POST("/broadcast", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
Message interface{} `json:"message"`
|
||||
Database string `json:"database,omitempty"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
websocketHandler.BroadcastMessage(req.Type, req.Message)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "broadcast sent",
|
||||
"clients_count": websocketHandler.GetConnectedClients(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
wsAPI.POST("/broadcast/room/:room", func(c *gin.Context) {
|
||||
room := c.Param("room")
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
Message interface{} `json:"message"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
websocketHandler.BroadcastToRoom(room, req.Type, req.Message)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "room broadcast sent",
|
||||
"room": room,
|
||||
"clients_count": websocketHandler.GetRoomClientCount(room), // Fix: gunakan GetRoomClientCount
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// ENHANCED CLIENT TARGETING
|
||||
// =============================================================================
|
||||
|
||||
wsAPI.POST("/send/:clientId", func(c *gin.Context) {
|
||||
clientID := c.Param("clientId")
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
Message interface{} `json:"message"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
websocketHandler.SendToClient(clientID, req.Type, req.Message)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "message sent",
|
||||
"client_id": clientID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// Send to client by static ID
|
||||
wsAPI.POST("/send/static/:staticId", func(c *gin.Context) {
|
||||
staticID := c.Param("staticId")
|
||||
logger.Infof("Sending message to static client: %s", staticID)
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
Message interface{} `json:"message"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
success := websocketHandler.SendToClientByStaticID(staticID, req.Type, req.Message)
|
||||
if success {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "message sent to static client",
|
||||
"static_id": staticID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
} else {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "static client not found",
|
||||
"static_id": staticID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Broadcast to all clients from specific IP
|
||||
wsAPI.POST("/broadcast/ip/:ipAddress", func(c *gin.Context) {
|
||||
ipAddress := c.Param("ipAddress")
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
Message interface{} `json:"message"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
count := websocketHandler.BroadcastToIP(ipAddress, req.Type, req.Message)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "ip broadcast sent",
|
||||
"ip_address": ipAddress,
|
||||
"clients_count": count,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// CLIENT INFORMATION & STATISTICS
|
||||
// =============================================================================
|
||||
|
||||
wsAPI.GET("/stats", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"connected_clients": websocketHandler.GetConnectedClients(),
|
||||
"databases": dbService.ListDBs(),
|
||||
"database_health": dbService.Health(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
wsAPI.GET("/stats/detailed", func(c *gin.Context) {
|
||||
stats := websocketHandler.GetDetailedStats()
|
||||
c.JSON(200, gin.H{
|
||||
"stats": stats,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
wsAPI.GET("/clients", func(c *gin.Context) {
|
||||
clients := websocketHandler.GetAllClients()
|
||||
c.JSON(200, gin.H{
|
||||
"clients": clients,
|
||||
"count": len(clients),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// Fix: Perbaiki GetClientsByIP untuk menggunakan ClientInfo
|
||||
wsAPI.GET("/clients/by-ip/:ipAddress", func(c *gin.Context) {
|
||||
ipAddress := c.Param("ipAddress")
|
||||
client := websocketHandler.GetClientsByIP(ipAddress)
|
||||
if client == nil {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "client not found",
|
||||
"ip_address": ipAddress,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Use ClientInfo struct instead of direct field access
|
||||
clientInfo := websocketHandler.GetAllClients()
|
||||
var targetClientInfo *websocket.ClientInfo
|
||||
for i := range clientInfo {
|
||||
if clientInfo[i].ID == ipAddress {
|
||||
targetClientInfo = &clientInfo[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetClientInfo == nil {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "ipAddress not found",
|
||||
"client_id": ipAddress,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"client": map[string]interface{}{
|
||||
"id": targetClientInfo.ID,
|
||||
"static_id": targetClientInfo.StaticID,
|
||||
"ip_address": targetClientInfo.IPAddress,
|
||||
"user_id": targetClientInfo.UserID,
|
||||
"room": targetClientInfo.Room,
|
||||
"connected_at": targetClientInfo.ConnectedAt.Unix(), // Fixed: use exported field
|
||||
"last_ping": targetClientInfo.LastPing.Unix(), // Fixed: use exported field
|
||||
},
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// Fix: Perbaiki GetClientByID response
|
||||
wsAPI.GET("/client/:clientId", func(c *gin.Context) {
|
||||
clientID := c.Param("clientId")
|
||||
client := websocketHandler.GetClientByID(clientID)
|
||||
|
||||
if client == nil {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "client not found",
|
||||
"client_id": clientID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Use ClientInfo struct instead of direct field access
|
||||
clientInfo := websocketHandler.GetAllClients()
|
||||
var targetClientInfo *websocket.ClientInfo
|
||||
for i := range clientInfo {
|
||||
if clientInfo[i].ID == clientID {
|
||||
targetClientInfo = &clientInfo[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetClientInfo == nil {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "client not found",
|
||||
"client_id": clientID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"client": map[string]interface{}{
|
||||
"id": targetClientInfo.ID,
|
||||
"static_id": targetClientInfo.StaticID,
|
||||
"ip_address": targetClientInfo.IPAddress,
|
||||
"user_id": targetClientInfo.UserID,
|
||||
"room": targetClientInfo.Room,
|
||||
"connected_at": targetClientInfo.ConnectedAt.Unix(), // Fixed: use exported field
|
||||
"last_ping": targetClientInfo.LastPing.Unix(), // Fixed: use exported field
|
||||
},
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// Fix: Perbaiki GetClientByStaticID response
|
||||
wsAPI.GET("/client/static/:staticId", func(c *gin.Context) {
|
||||
staticID := c.Param("staticId")
|
||||
client := websocketHandler.GetClientByStaticID(staticID)
|
||||
|
||||
if client == nil {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "static client not found",
|
||||
"static_id": staticID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Use ClientInfo struct instead of direct field access
|
||||
clientInfo := websocketHandler.GetAllClients()
|
||||
var targetClientInfo *websocket.ClientInfo
|
||||
for i := range clientInfo {
|
||||
if clientInfo[i].StaticID == staticID {
|
||||
targetClientInfo = &clientInfo[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetClientInfo == nil {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "static client not found",
|
||||
"static_id": staticID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"client": map[string]interface{}{
|
||||
"id": targetClientInfo.ID,
|
||||
"static_id": targetClientInfo.StaticID,
|
||||
"ip_address": targetClientInfo.IPAddress,
|
||||
"user_id": targetClientInfo.UserID,
|
||||
"room": targetClientInfo.Room,
|
||||
"connected_at": targetClientInfo.ConnectedAt.Unix(), // Fixed: use exported field
|
||||
"last_ping": targetClientInfo.LastPing.Unix(), // Fixed: use exported field
|
||||
},
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// ACTIVE CLIENTS & CLEANUP
|
||||
// =============================================================================
|
||||
|
||||
// Tambahkan endpoint untuk active clients
|
||||
wsAPI.GET("/clients/active", func(c *gin.Context) {
|
||||
// Default: clients active dalam 5 menit terakhir
|
||||
minutes := c.DefaultQuery("minutes", "5")
|
||||
minutesInt, err := strconv.Atoi(minutes)
|
||||
if err != nil {
|
||||
minutesInt = 5
|
||||
}
|
||||
|
||||
activeClients := websocketHandler.GetActiveClients(time.Duration(minutesInt) * time.Minute)
|
||||
c.JSON(200, gin.H{
|
||||
"active_clients": activeClients,
|
||||
"count": len(activeClients),
|
||||
"threshold_minutes": minutesInt,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// Tambahkan endpoint untuk cleanup inactive clients
|
||||
wsAPI.POST("/cleanup/inactive", func(c *gin.Context) {
|
||||
var req struct {
|
||||
InactiveMinutes int `json:"inactive_minutes"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
req.InactiveMinutes = 30 // Default 30 minutes
|
||||
}
|
||||
|
||||
if req.InactiveMinutes <= 0 {
|
||||
req.InactiveMinutes = 30
|
||||
}
|
||||
|
||||
cleanedCount := websocketHandler.CleanupInactiveClients(time.Duration(req.InactiveMinutes) * time.Minute)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "cleanup completed",
|
||||
"cleaned_clients": cleanedCount,
|
||||
"inactive_minutes": req.InactiveMinutes,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// DATABASE NOTIFICATIONS
|
||||
// =============================================================================
|
||||
|
||||
wsAPI.POST("/notify/:database/:channel", func(c *gin.Context) {
|
||||
database := c.Param("database")
|
||||
channel := c.Param("channel")
|
||||
|
||||
var req struct {
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
payloadJSON, _ := json.Marshal(req.Payload)
|
||||
err := dbService.NotifyChange(database, channel, string(payloadJSON))
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"error": err.Error(),
|
||||
"database": database,
|
||||
"channel": channel,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "notification sent",
|
||||
"database": database,
|
||||
"channel": channel,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// Test database notification
|
||||
wsAPI.POST("/test-notification", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Database string `json:"database"`
|
||||
Channel string `json:"channel"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Default values
|
||||
if req.Database == "" {
|
||||
req.Database = "default"
|
||||
}
|
||||
if req.Channel == "" {
|
||||
req.Channel = "system_changes"
|
||||
}
|
||||
if req.Message == "" {
|
||||
req.Message = "Test notification from API"
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"operation": "API_TEST",
|
||||
"table": "manual_test",
|
||||
"data": map[string]interface{}{
|
||||
"message": req.Message,
|
||||
"test_data": req.Data,
|
||||
"timestamp": time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
payloadJSON, _ := json.Marshal(payload)
|
||||
err := dbService.NotifyChange(req.Database, req.Channel, string(payloadJSON))
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "test notification sent",
|
||||
"database": req.Database,
|
||||
"channel": req.Channel,
|
||||
"payload": payload,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// ROOM MANAGEMENT
|
||||
// =============================================================================
|
||||
|
||||
wsAPI.GET("/rooms", func(c *gin.Context) {
|
||||
rooms := websocketHandler.GetAllRooms()
|
||||
c.JSON(200, gin.H{
|
||||
"rooms": rooms,
|
||||
"count": len(rooms),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
wsAPI.GET("/room/:room/clients", func(c *gin.Context) {
|
||||
room := c.Param("room")
|
||||
clientCount := websocketHandler.GetRoomClientCount(room)
|
||||
|
||||
// Get detailed room info
|
||||
allRooms := websocketHandler.GetAllRooms()
|
||||
roomClients := allRooms[room]
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"room": room,
|
||||
"client_count": clientCount,
|
||||
"clients": roomClients,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// MONITORING & DEBUGGING
|
||||
// =============================================================================
|
||||
|
||||
wsAPI.GET("/monitor", func(c *gin.Context) {
|
||||
monitor := websocketHandler.GetMonitoringData()
|
||||
c.JSON(200, monitor)
|
||||
})
|
||||
|
||||
wsAPI.POST("/ping-client/:clientId", func(c *gin.Context) {
|
||||
clientID := c.Param("clientId")
|
||||
websocketHandler.SendToClient(clientID, "server_ping", map[string]interface{}{
|
||||
"message": "Ping from server",
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
c.JSON(200, gin.H{
|
||||
"status": "ping sent",
|
||||
"client_id": clientID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// Disconnect specific client
|
||||
wsAPI.POST("/disconnect/:clientId", func(c *gin.Context) {
|
||||
clientID := c.Param("clientId")
|
||||
success := websocketHandler.DisconnectClient(clientID)
|
||||
if success {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "client disconnected",
|
||||
"client_id": clientID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
} else {
|
||||
c.JSON(404, gin.H{
|
||||
"error": "client not found",
|
||||
"client_id": clientID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// BULK OPERATIONS
|
||||
// =============================================================================
|
||||
|
||||
// Broadcast to multiple clients
|
||||
wsAPI.POST("/broadcast/bulk", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ClientIDs []string `json:"client_ids"`
|
||||
Type string `json:"type"`
|
||||
Message interface{} `json:"message"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
successCount := 0
|
||||
for _, clientID := range req.ClientIDs {
|
||||
websocketHandler.SendToClient(clientID, req.Type, req.Message)
|
||||
successCount++
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "bulk broadcast sent",
|
||||
"total_clients": len(req.ClientIDs),
|
||||
"success_count": successCount,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
// Disconnect multiple clients
|
||||
wsAPI.POST("/disconnect/bulk", func(c *gin.Context) {
|
||||
var req struct {
|
||||
ClientIDs []string `json:"client_ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
successCount := 0
|
||||
for _, clientID := range req.ClientIDs {
|
||||
if websocketHandler.DisconnectClient(clientID) {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "bulk disconnect completed",
|
||||
"total_clients": len(req.ClientIDs),
|
||||
"success_count": successCount,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PUBLISHED ROUTES
|
||||
// =============================================================================
|
||||
|
||||
// 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)
|
||||
|
||||
// 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 {
|
||||
websocketHandler.BroadcastMessage("retribusi_created", map[string]interface{}{
|
||||
"message": "New retribusi record created",
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
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 {
|
||||
websocketHandler.BroadcastMessage("retribusi_updated", map[string]interface{}{
|
||||
"message": "Retribusi record updated",
|
||||
"id": id,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
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 {
|
||||
websocketHandler.BroadcastMessage("retribusi_deleted", map[string]interface{}{
|
||||
"message": "Retribusi record deleted",
|
||||
"id": id,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PROTECTED ROUTES (Authentication Required)
|
||||
// =============================================================================
|
||||
|
||||
protected := v1.Group("/")
|
||||
protected.Use(middleware.ConfigurableAuthMiddleware(cfg))
|
||||
|
||||
// Protected WebSocket management (optional)
|
||||
protectedWS := protected.Group("/ws-admin")
|
||||
{
|
||||
protectedWS.GET("/stats", func(c *gin.Context) {
|
||||
detailedStats := websocketHandler.GetDetailedStats()
|
||||
c.JSON(200, gin.H{
|
||||
"admin_stats": detailedStats,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
protectedWS.POST("/force-disconnect/:clientId", func(c *gin.Context) {
|
||||
clientID := c.Param("clientId")
|
||||
success := websocketHandler.DisconnectClient(clientID)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "force disconnect attempted",
|
||||
"client_id": clientID,
|
||||
"success": success,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
|
||||
protectedWS.POST("/cleanup/force", func(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 := websocketHandler.CleanupInactiveClients(time.Duration(req.InactiveMinutes) * time.Minute)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "admin cleanup completed",
|
||||
"cleaned_clients": cleanedCount,
|
||||
"inactive_minutes": req.InactiveMinutes,
|
||||
"force": req.Force,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
Reference in New Issue
Block a user