Initial commit
This commit is contained in:
+28
-26
@@ -1,25 +1,26 @@
|
|||||||
services:
|
services:
|
||||||
# Main Application
|
# Main Application
|
||||||
app:
|
app:
|
||||||
|
container_name: websocket-qris
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
target: prod
|
target: prod
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8070:8070"
|
- "8030:8030"
|
||||||
environment:
|
environment:
|
||||||
# Server Configuration
|
# Server Configuration
|
||||||
APP_ENV: production
|
APP_ENV: production
|
||||||
PORT: 8070
|
PORT: 8030
|
||||||
GIN_MODE: release
|
GIN_MODE: release
|
||||||
|
|
||||||
# Default Database Configuration (PostgreSQL)
|
# Default Database Configuration (PostgreSQL)
|
||||||
DB_CONNECTION: postgres
|
DB_CONNECTION: postgres
|
||||||
DB_USERNAME: stim
|
DB_USERNAME: simtest
|
||||||
DB_PASSWORD: stim*RS54
|
DB_PASSWORD: 12345
|
||||||
DB_HOST: 10.10.123.165
|
DB_HOST: 10.10.123.223
|
||||||
DB_DATABASE: satu_db
|
DB_DATABASE: simrsbackup
|
||||||
DB_PORT: 5432
|
DB_PORT: 5432
|
||||||
DB_SSLMODE: disable
|
DB_SSLMODE: disable
|
||||||
|
|
||||||
@@ -61,30 +62,30 @@ services:
|
|||||||
# MYSQL_MEDICAL_SSLMODE: disable
|
# MYSQL_MEDICAL_SSLMODE: disable
|
||||||
|
|
||||||
# Keycloak Configuration
|
# Keycloak Configuration
|
||||||
KEYCLOAK_ISSUER: https://auth.rssa.top/realms/sandbox
|
# KEYCLOAK_ISSUER: https://auth.rssa.top/realms/sandbox
|
||||||
KEYCLOAK_AUDIENCE: nuxtsim-pendaftaran
|
# KEYCLOAK_AUDIENCE: nuxtsim-pendaftaran
|
||||||
KEYCLOAK_JWKS_URL: https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs
|
# KEYCLOAK_JWKS_URL: https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs
|
||||||
KEYCLOAK_ENABLED: "true"
|
# KEYCLOAK_ENABLED: "true"
|
||||||
|
|
||||||
# BPJS Configuration
|
# BPJS Configuration
|
||||||
BPJS_BASEURL: https://apijkn.bpjs-kesehatan.go.id/vclaim-rest
|
# BPJS_BASEURL: https://apijkn.bpjs-kesehatan.go.id/vclaim-rest
|
||||||
BPJS_CONSID: 5257
|
# BPJS_CONSID: 5257
|
||||||
BPJS_USERKEY: 4cf1cbef8c008440bbe9ef9ba789e482
|
# BPJS_USERKEY: 4cf1cbef8c008440bbe9ef9ba789e482
|
||||||
BPJS_SECRETKEY: 1bV363512D
|
# BPJS_SECRETKEY: 1bV363512D
|
||||||
|
|
||||||
# SatuSehat Configuration
|
# SatuSehat Configuration
|
||||||
BRIDGING_SATUSEHAT_ORG_ID: 100026555
|
# BRIDGING_SATUSEHAT_ORG_ID: 100026555
|
||||||
BRIDGING_SATUSEHAT_FASYAKES_ID: 3573011
|
# BRIDGING_SATUSEHAT_FASYAKES_ID: 3573011
|
||||||
BRIDGING_SATUSEHAT_CLIENT_ID: l1ZgJGW6K5pnrqGUikWM7fgIoquA2AQ5UUG0U8WqHaq2VEyZ
|
# BRIDGING_SATUSEHAT_CLIENT_ID: l1ZgJGW6K5pnrqGUikWM7fgIoquA2AQ5UUG0U8WqHaq2VEyZ
|
||||||
BRIDGING_SATUSEHAT_CLIENT_SECRET: Al3PTYAW6axPiAFwaFlpn8qShLFW5YGMgG8w1qhexgCc7lGTEjjcR6zxa06ThPDy
|
# BRIDGING_SATUSEHAT_CLIENT_SECRET: Al3PTYAW6axPiAFwaFlpn8qShLFW5YGMgG8w1qhexgCc7lGTEjjcR6zxa06ThPDy
|
||||||
BRIDGING_SATUSEHAT_AUTH_URL: https://api-satusehat.kemkes.go.id/oauth2/v1
|
# BRIDGING_SATUSEHAT_AUTH_URL: https://api-satusehat.kemkes.go.id/oauth2/v1
|
||||||
BRIDGING_SATUSEHAT_BASE_URL: https://api-satusehat.kemkes.go.id/fhir-r4/v1
|
# BRIDGING_SATUSEHAT_BASE_URL: https://api-satusehat.kemkes.go.id/fhir-r4/v1
|
||||||
BRIDGING_SATUSEHAT_CONSENT_URL: https://api-satusehat.dto.kemkes.go.id/consent/v1
|
# BRIDGING_SATUSEHAT_CONSENT_URL: https://api-satusehat.dto.kemkes.go.id/consent/v1
|
||||||
BRIDGING_SATUSEHAT_KFA_URL: https://api-satusehat.kemkes.go.id/kfa-v2
|
# BRIDGING_SATUSEHAT_KFA_URL: https://api-satusehat.kemkes.go.id/kfa-v2
|
||||||
|
|
||||||
# Swagger Configuration
|
# Swagger Configuration
|
||||||
SWAGGER_TITLE: My Custom API Service
|
SWAGGER_TITLE: API Service QRIS
|
||||||
SWAGGER_DESCRIPTION: This is a custom API service for managing various resources
|
SWAGGER_DESCRIPTION: Documentation SWAGGER API Service QRIS
|
||||||
SWAGGER_VERSION: 2.0.0
|
SWAGGER_VERSION: 2.0.0
|
||||||
SWAGGER_CONTACT_NAME: Support Team
|
SWAGGER_CONTACT_NAME: Support Team
|
||||||
SWAGGER_HOST: api.mycompany.com:8080
|
SWAGGER_HOST: api.mycompany.com:8080
|
||||||
@@ -92,9 +93,10 @@ services:
|
|||||||
SWAGGER_SCHEMES: https
|
SWAGGER_SCHEMES: https
|
||||||
|
|
||||||
# API Configuration
|
# API Configuration
|
||||||
API_TITLE: API Service UJICOBA
|
API_TITLE: API Service UJICOBA QRIS
|
||||||
API_DESCRIPTION: Dokumentation SWAGGER
|
API_DESCRIPTION: Documentation SWAGGER
|
||||||
API_VERSION: 3.0.0
|
API_VERSION: 3.0.0
|
||||||
|
|
||||||
# WebSocket Configuration
|
# WebSocket Configuration
|
||||||
WS_READ_TIMEOUT: 300s
|
WS_READ_TIMEOUT: 300s
|
||||||
WS_WRITE_TIMEOUT: 30s
|
WS_WRITE_TIMEOUT: 30s
|
||||||
|
|||||||
+58
-1812
File diff suppressed because it is too large
Load Diff
+58
-1812
File diff suppressed because it is too large
Load Diff
+52
-1208
File diff suppressed because it is too large
Load Diff
@@ -821,9 +821,7 @@
|
|||||||
);
|
);
|
||||||
const ipBased = document.getElementById("ipBasedCheck").checked;
|
const ipBased = document.getElementById("ipBasedCheck").checked;
|
||||||
|
|
||||||
let url = `ws://meninjar.dev.rssa.id:8070/api/v1/ws?user_id=${encodeURIComponent(
|
let url = `ws://localhost:8080/api/v1/ws?user_id=QRIS&room=BANKJATIM`;
|
||||||
userId
|
|
||||||
)}&room=${encodeURIComponent(room)}`;
|
|
||||||
|
|
||||||
if (ipBased) {
|
if (ipBased) {
|
||||||
url += "&ip_based=true";
|
url += "&ip_based=true";
|
||||||
|
|||||||
+30
-30
@@ -761,18 +761,18 @@ func (c *Config) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Bpjs.BaseURL == "" {
|
// if c.Bpjs.BaseURL == "" {
|
||||||
log.Fatal("BPJS Base URL is required")
|
// log.Fatal("BPJS Base URL is required")
|
||||||
}
|
// }
|
||||||
if c.Bpjs.ConsID == "" {
|
// if c.Bpjs.ConsID == "" {
|
||||||
log.Fatal("BPJS Consumer ID is required")
|
// log.Fatal("BPJS Consumer ID is required")
|
||||||
}
|
// }
|
||||||
if c.Bpjs.UserKey == "" {
|
// if c.Bpjs.UserKey == "" {
|
||||||
log.Fatal("BPJS User Key is required")
|
// log.Fatal("BPJS User Key is required")
|
||||||
}
|
// }
|
||||||
if c.Bpjs.SecretKey == "" {
|
// if c.Bpjs.SecretKey == "" {
|
||||||
log.Fatal("BPJS Secret Key is required")
|
// log.Fatal("BPJS Secret Key is required")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Validate Keycloak configuration if enabled
|
// Validate Keycloak configuration if enabled
|
||||||
if c.Keycloak.Enabled {
|
if c.Keycloak.Enabled {
|
||||||
@@ -788,24 +788,24 @@ func (c *Config) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate SatuSehat configuration
|
// Validate SatuSehat configuration
|
||||||
if c.SatuSehat.OrgID == "" {
|
// if c.SatuSehat.OrgID == "" {
|
||||||
log.Fatal("SatuSehat Organization ID is required")
|
// log.Fatal("SatuSehat Organization ID is required")
|
||||||
}
|
// }
|
||||||
if c.SatuSehat.FasyakesID == "" {
|
// if c.SatuSehat.FasyakesID == "" {
|
||||||
log.Fatal("SatuSehat Fasyankes ID is required")
|
// log.Fatal("SatuSehat Fasyankes ID is required")
|
||||||
}
|
// }
|
||||||
if c.SatuSehat.ClientID == "" {
|
// if c.SatuSehat.ClientID == "" {
|
||||||
log.Fatal("SatuSehat Client ID is required")
|
// log.Fatal("SatuSehat Client ID is required")
|
||||||
}
|
// }
|
||||||
if c.SatuSehat.ClientSecret == "" {
|
// if c.SatuSehat.ClientSecret == "" {
|
||||||
log.Fatal("SatuSehat Client Secret is required")
|
// log.Fatal("SatuSehat Client Secret is required")
|
||||||
}
|
// }
|
||||||
if c.SatuSehat.AuthURL == "" {
|
// if c.SatuSehat.AuthURL == "" {
|
||||||
log.Fatal("SatuSehat Auth URL is required")
|
// log.Fatal("SatuSehat Auth URL is required")
|
||||||
}
|
// }
|
||||||
if c.SatuSehat.BaseURL == "" {
|
// if c.SatuSehat.BaseURL == "" {
|
||||||
log.Fatal("SatuSehat Base URL is required")
|
// log.Fatal("SatuSehat Base URL is required")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Validate WebSocket configuration
|
// Validate WebSocket configuration
|
||||||
if c.WebSocket.ReadTimeout <= 0 {
|
if c.WebSocket.ReadTimeout <= 0 {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,8 @@ package websocket
|
|||||||
import (
|
import (
|
||||||
"api-service/internal/config"
|
"api-service/internal/config"
|
||||||
ws "api-service/internal/services/websocket"
|
ws "api-service/internal/services/websocket"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -88,7 +90,7 @@ func (h *WebSocketHandler) TestWebSocketConnection(c *gin.Context) {
|
|||||||
"user_id": "optional, for user identification",
|
"user_id": "optional, for user identification",
|
||||||
"room": "optional, for room-based messaging",
|
"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(),
|
"timestamp": time.Now().Unix(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -134,3 +136,134 @@ func (h *WebSocketHandler) CleanupInactiveClients(c *gin.Context) {
|
|||||||
"timestamp": time.Now().Unix(),
|
"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
|
||||||
|
}
|
||||||
@@ -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"`
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,7 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"api-service/internal/config"
|
"api-service/internal/config"
|
||||||
"api-service/internal/database"
|
"api-service/internal/database"
|
||||||
authHandlers "api-service/internal/handlers/auth"
|
|
||||||
healthcheckHandlers "api-service/internal/handlers/healthcheck"
|
healthcheckHandlers "api-service/internal/handlers/healthcheck"
|
||||||
retribusiHandlers "api-service/internal/handlers/retribusi"
|
|
||||||
websocketHandlers "api-service/internal/handlers/websocket"
|
websocketHandlers "api-service/internal/handlers/websocket"
|
||||||
"api-service/internal/middleware"
|
"api-service/internal/middleware"
|
||||||
services "api-service/internal/services/auth"
|
services "api-service/internal/services/auth"
|
||||||
@@ -22,7 +20,7 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
|||||||
router := gin.New()
|
router := gin.New()
|
||||||
|
|
||||||
// Initialize auth middleware configuration
|
// Initialize auth middleware configuration
|
||||||
middleware.InitializeAuth(cfg)
|
middleware.AuthJWTMiddleware()
|
||||||
|
|
||||||
// Add global middleware
|
// Add global middleware
|
||||||
router.Use(middleware.CORSConfig())
|
router.Use(middleware.CORSConfig())
|
||||||
@@ -90,17 +88,17 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// Authentication routes
|
// Authentication routes
|
||||||
authHandler := authHandlers.NewAuthHandler(authService)
|
// authHandler := authHandlers.NewAuthHandler(authService)
|
||||||
tokenHandler := authHandlers.NewTokenHandler(authService)
|
// tokenHandler := authHandlers.NewTokenHandler(authService)
|
||||||
|
|
||||||
// Basic auth routes
|
// Basic auth routes
|
||||||
v1.POST("/auth/login", authHandler.Login)
|
// v1.POST("/auth/login", authHandler.Login)
|
||||||
v1.POST("/auth/register", authHandler.Register)
|
// v1.POST("/auth/register", authHandler.Register)
|
||||||
v1.POST("/auth/refresh", authHandler.RefreshToken)
|
// v1.POST("/auth/refresh", authHandler.RefreshToken)
|
||||||
|
|
||||||
// Token generation routes
|
// Token generation routes
|
||||||
v1.POST("/token/generate", tokenHandler.GenerateToken)
|
// v1.POST("/token/generate", tokenHandler.GenerateToken)
|
||||||
v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
|
// v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// WEBSOCKET ROUTES
|
// WEBSOCKET ROUTES
|
||||||
@@ -123,52 +121,72 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
|||||||
v1.GET("/ws", websocketHandler.HandleWebSocket)
|
v1.GET("/ws", websocketHandler.HandleWebSocket)
|
||||||
v1.GET("/ws/test", websocketHandler.TestWebSocketConnection)
|
v1.GET("/ws/test", websocketHandler.TestWebSocketConnection)
|
||||||
v1.GET("/ws/stats", websocketHandler.GetWebSocketStats)
|
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
|
// Retribusi endpoints with WebSocket notifications
|
||||||
retribusiHandler := retribusiHandlers.NewRetribusiHandler()
|
// retribusiHandler := retribusiHandlers.NewRetribusiHandler()
|
||||||
retribusiGroup := v1.Group("/retribusi")
|
// retribusiGroup := v1.Group("/retribusi")
|
||||||
{
|
// {
|
||||||
retribusiGroup.GET("", retribusiHandler.GetRetribusi)
|
// retribusiGroup.GET("", retribusiHandler.GetRetribusi)
|
||||||
retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic)
|
// retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic)
|
||||||
retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced)
|
// retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced)
|
||||||
retribusiGroup.GET("/id/:id", retribusiHandler.GetRetribusiByID)
|
// retribusiGroup.GET("/id/:id", retribusiHandler.GetRetribusiByID)
|
||||||
|
|
||||||
// POST/PUT/DELETE with automatic WebSocket notifications
|
// // POST/PUT/DELETE with automatic WebSocket notifications
|
||||||
retribusiGroup.POST("", func(c *gin.Context) {
|
// retribusiGroup.POST("", func(c *gin.Context) {
|
||||||
retribusiHandler.CreateRetribusi(c)
|
// retribusiHandler.CreateRetribusi(c)
|
||||||
|
|
||||||
// Trigger WebSocket notification after successful creation
|
// // Trigger WebSocket notification after successful creation
|
||||||
if c.Writer.Status() == 200 || c.Writer.Status() == 201 {
|
// if c.Writer.Status() == 200 || c.Writer.Status() == 201 {
|
||||||
// Notify database change via WebSocket
|
// // Notify database change via WebSocket
|
||||||
// websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
|
// // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
|
||||||
// fmt.Sprintf(`{"action": "created", "timestamp": "%s"}`, time.Now().Format(time.RFC3339)))
|
// // fmt.Sprintf(`{"action": "created", "timestamp": "%s"}`, time.Now().Format(time.RFC3339)))
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
retribusiGroup.PUT("/id/:id", func(c *gin.Context) {
|
// retribusiGroup.PUT("/id/:id", func(c *gin.Context) {
|
||||||
// id := c.Param("id")
|
// // id := c.Param("id")
|
||||||
retribusiHandler.UpdateRetribusi(c)
|
// retribusiHandler.UpdateRetribusi(c)
|
||||||
|
|
||||||
// Trigger WebSocket notification after successful update
|
// // Trigger WebSocket notification after successful update
|
||||||
if c.Writer.Status() == 200 {
|
// if c.Writer.Status() == 200 {
|
||||||
// Notify database change via WebSocket
|
// // Notify database change via WebSocket
|
||||||
// websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
|
// // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
|
||||||
// fmt.Sprintf(`{"action": "updated", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339)))
|
// // fmt.Sprintf(`{"action": "updated", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339)))
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
retribusiGroup.DELETE("/id/:id", func(c *gin.Context) {
|
// retribusiGroup.DELETE("/id/:id", func(c *gin.Context) {
|
||||||
// id := c.Param("id")
|
// // id := c.Param("id")
|
||||||
retribusiHandler.DeleteRetribusi(c)
|
// retribusiHandler.DeleteRetribusi(c)
|
||||||
|
|
||||||
// Trigger WebSocket notification after successful deletion
|
// // Trigger WebSocket notification after successful deletion
|
||||||
if c.Writer.Status() == 200 {
|
// if c.Writer.Status() == 200 {
|
||||||
// Notify database change via WebSocket
|
// // Notify database change via WebSocket
|
||||||
// websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
|
// // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes",
|
||||||
// fmt.Sprintf(`{"action": "deleted", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339)))
|
// // 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
|
return router
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"api-service/pkg/logger"
|
||||||
"time"
|
"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
|
// BroadcastToRoom mengirim pesan ke ruangan tertentu
|
||||||
func (b *Broadcaster) BroadcastToRoom(room string, messageType string, data interface{}) {
|
func (b *Broadcaster) BroadcastToRoom(room string, messageType string, data interface{}) {
|
||||||
msg := NewWebSocketMessage(
|
msg := NewWebSocketMessage(
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (h *DatabaseHandler) handleDatabaseInsert(client *Client, message WebSocket
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
table, ok := data["table"].(string)
|
table, ok := data["m_deviceqris"].(string)
|
||||||
if !ok || table == "" {
|
if !ok || table == "" {
|
||||||
client.sendErrorResponse("Invalid table name", "table is required")
|
client.sendErrorResponse("Invalid table name", "table is required")
|
||||||
return nil
|
return nil
|
||||||
@@ -55,7 +55,7 @@ func (h *DatabaseHandler) handleDatabaseInsert(client *Client, message WebSocket
|
|||||||
// Perform actual database insert
|
// Perform actual database insert
|
||||||
if h.hub.dbService != nil {
|
if h.hub.dbService != nil {
|
||||||
// Get database connection
|
// Get database connection
|
||||||
db, err := h.hub.GetDatabaseConnection("postgres_satudata")
|
db, err := h.hub.GetDatabaseConnection("simrs_backup")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.sendErrorResponse("Database connection error", err.Error())
|
client.sendErrorResponse("Database connection error", err.Error())
|
||||||
return nil
|
return nil
|
||||||
@@ -110,14 +110,14 @@ func (h *DatabaseHandler) handleDatabaseQuery(client *Client, message WebSocketM
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
table, ok := data["table"].(string)
|
table, ok := data["m_deviceqris"].(string)
|
||||||
if !ok || table == "" {
|
if !ok || table == "" {
|
||||||
client.sendErrorResponse("Invalid table name", "table is required")
|
client.sendErrorResponse("Invalid table name", "table is required")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute query
|
// 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 {
|
if err != nil {
|
||||||
client.sendErrorResponse("Database query error", err.Error())
|
client.sendErrorResponse("Database query error", err.Error())
|
||||||
return nil
|
return nil
|
||||||
@@ -142,7 +142,7 @@ func (h *DatabaseHandler) handleDatabaseCustomQuery(client *Client, message WebS
|
|||||||
|
|
||||||
database, ok := data["database"].(string)
|
database, ok := data["database"].(string)
|
||||||
if !ok || database == "" {
|
if !ok || database == "" {
|
||||||
database = "postgres_satudata"
|
database = "simrs_backup"
|
||||||
}
|
}
|
||||||
|
|
||||||
query, ok := data["query"].(string)
|
query, ok := data["query"].(string)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ type ActivityLog struct {
|
|||||||
// DatabaseService mendefinisikan interface untuk layanan database
|
// DatabaseService mendefinisikan interface untuk layanan database
|
||||||
type DatabaseService interface {
|
type DatabaseService interface {
|
||||||
Health() map[string]interface{}
|
Health() map[string]interface{}
|
||||||
ListDBs() []string
|
//ListDBs() []string
|
||||||
ListenForChanges(ctx context.Context, dbName string, channels []string, callback func(channel, payload string)) error
|
ListenForChanges(ctx context.Context, dbName string, channels []string, callback func(channel, payload string)) error
|
||||||
NotifyChange(dbName, channel, payload string) error
|
NotifyChange(dbName, channel, payload string) error
|
||||||
GetDB(name string) (*sql.DB, 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
|
// setupDatabaseListeners sets up database change listeners for real-time updates
|
||||||
func (h *Hub) setupDatabaseListeners() {
|
func (h *Hub) setupDatabaseListeners() {
|
||||||
// Listen for changes on retribusi table
|
// 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)
|
h.handleDatabaseChange(channel, payload)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,67 @@ global:
|
|||||||
enable_swagger: true
|
enable_swagger: true
|
||||||
enable_logging: true
|
enable_logging: true
|
||||||
|
|
||||||
|
# services:
|
||||||
|
# retribusi:
|
||||||
|
# name: "Retribusi"
|
||||||
|
# category: "retribusi"
|
||||||
|
# package: "retribusi"
|
||||||
|
# description: "Retribusi service for tariff and billing management"
|
||||||
|
# base_url: ""
|
||||||
|
# timeout: 30
|
||||||
|
# retry_count: 3
|
||||||
|
|
||||||
services:
|
services:
|
||||||
retribusi:
|
qris:
|
||||||
name: "Retribusi"
|
name: "QRIS"
|
||||||
category: "retribusi"
|
category: "qris"
|
||||||
package: "retribusi"
|
package: "qris"
|
||||||
description: "Retribusi service for tariff and billing management"
|
description: "QRIS service for QR code payment management"
|
||||||
base_url: ""
|
base_url: ""
|
||||||
timeout: 30
|
timeout: 30
|
||||||
retry_count: 3
|
retry_count: 3
|
||||||
|
|
||||||
endpoints:
|
endpoints:
|
||||||
|
qris:
|
||||||
|
description: "QRIS management"
|
||||||
|
handler_folder: "qris"
|
||||||
|
handler_file: "qris.go"
|
||||||
|
handler_name: "QRIS"
|
||||||
|
table_name: "m_deviceqris"
|
||||||
|
functions:
|
||||||
|
create:
|
||||||
|
methods: ["POST"]
|
||||||
|
path: "/broadcast/qris"
|
||||||
|
post_routes: "/broadcast/qris"
|
||||||
|
post_path: "/broadcast/qris"
|
||||||
|
model: "QrisCreateRequest"
|
||||||
|
response_model: "QrisCreateResponse"
|
||||||
|
request_model: "QrisCreateRequest"
|
||||||
|
description: "Send new QRIS broadcast"
|
||||||
|
summary: "Send QRIS"
|
||||||
|
tags: ["QRIS"]
|
||||||
|
require_auth: true
|
||||||
|
cache_enabled: false
|
||||||
|
enable_database: true
|
||||||
|
cache_ttl: 0
|
||||||
|
|
||||||
|
create:
|
||||||
|
methods: ["POST"]
|
||||||
|
path: "/broadcast/check"
|
||||||
|
post_routes: "/broadcast/check"
|
||||||
|
post_path: "/broadcast/check"
|
||||||
|
model: "QrisCreateRequest"
|
||||||
|
response_model: "QrisCreateResponse"
|
||||||
|
request_model: "QrisCreateRequest"
|
||||||
|
description: "Send new QRIS check broadcast"
|
||||||
|
summary: "Send QRIS check"
|
||||||
|
tags: ["QRIS"]
|
||||||
|
require_auth: true
|
||||||
|
cache_enabled: false
|
||||||
|
enable_database: true
|
||||||
|
cache_ttl: 0
|
||||||
|
|
||||||
|
# endpoints:
|
||||||
# retribusi:
|
# retribusi:
|
||||||
# description: "Retribusi tariff management"
|
# description: "Retribusi tariff management"
|
||||||
# handler_folder: "retribusi"
|
# handler_folder: "retribusi"
|
||||||
@@ -152,99 +202,99 @@ services:
|
|||||||
# has_stats: true
|
# has_stats: true
|
||||||
|
|
||||||
# Example of another service
|
# Example of another service
|
||||||
user:
|
# user:
|
||||||
name: "User"
|
# name: "User"
|
||||||
category: "user"
|
# category: "user"
|
||||||
package: "user"
|
# package: "user"
|
||||||
description: "User management service"
|
# description: "User management service"
|
||||||
base_url: ""
|
# base_url: ""
|
||||||
timeout: 30
|
# timeout: 30
|
||||||
retry_count: 3
|
# retry_count: 3
|
||||||
|
|
||||||
endpoints:
|
# endpoints:
|
||||||
user:
|
# user:
|
||||||
description: "User management endpoints"
|
# description: "User management endpoints"
|
||||||
handler_folder: "retribusi"
|
# handler_folder: "retribusi"
|
||||||
handler_file: "user.go"
|
# handler_file: "user.go"
|
||||||
handler_name: "User"
|
# handler_name: "User"
|
||||||
table_name: "data_user"
|
# table_name: "data_user"
|
||||||
functions:
|
# functions:
|
||||||
list:
|
# list:
|
||||||
methods: ["GET"]
|
# methods: ["GET"]
|
||||||
path: "/"
|
# path: "/"
|
||||||
get_routes: "/"
|
# get_routes: "/"
|
||||||
get_path: "/"
|
# get_path: "/"
|
||||||
model: "User"
|
# model: "User"
|
||||||
response_model: "UserGetResponse"
|
# response_model: "UserGetResponse"
|
||||||
description: "Get user list with pagination"
|
# description: "Get user list with pagination"
|
||||||
summary: "Get User List"
|
# summary: "Get User List"
|
||||||
tags: ["User"]
|
# tags: ["User"]
|
||||||
require_auth: true
|
# require_auth: true
|
||||||
cache_enabled: true
|
# cache_enabled: true
|
||||||
enable_database: true
|
# enable_database: true
|
||||||
cache_ttl: 300
|
# cache_ttl: 300
|
||||||
has_pagination: true
|
# has_pagination: true
|
||||||
has_filter: true
|
# has_filter: true
|
||||||
has_search: true
|
# has_search: true
|
||||||
|
|
||||||
get:
|
# get:
|
||||||
methods: ["GET"]
|
# methods: ["GET"]
|
||||||
path: "/:id"
|
# path: "/:id"
|
||||||
get_routes: "/:id"
|
# get_routes: "/:id"
|
||||||
get_path: "/:id"
|
# get_path: "/:id"
|
||||||
model: "User"
|
# model: "User"
|
||||||
response_model: "UserGetByIDResponse"
|
# response_model: "UserGetByIDResponse"
|
||||||
description: "Get user by ID"
|
# description: "Get user by ID"
|
||||||
summary: "Get User by ID"
|
# summary: "Get User by ID"
|
||||||
tags: ["User"]
|
# tags: ["User"]
|
||||||
require_auth: true
|
# require_auth: true
|
||||||
cache_enabled: true
|
# cache_enabled: true
|
||||||
enable_database: true
|
# enable_database: true
|
||||||
cache_ttl: 300
|
# cache_ttl: 300
|
||||||
|
|
||||||
create:
|
# create:
|
||||||
methods: ["POST"]
|
# methods: ["POST"]
|
||||||
path: "/"
|
# path: "/"
|
||||||
post_routes: "/"
|
# post_routes: "/"
|
||||||
post_path: "/"
|
# post_path: "/"
|
||||||
model: "UserCreateRequest"
|
# model: "UserCreateRequest"
|
||||||
response_model: "UserCreateResponse"
|
# response_model: "UserCreateResponse"
|
||||||
request_model: "UserCreateRequest"
|
# request_model: "UserCreateRequest"
|
||||||
description: "Create new user"
|
# description: "Create new user"
|
||||||
summary: "Create User"
|
# summary: "Create User"
|
||||||
tags: ["User"]
|
# tags: ["User"]
|
||||||
require_auth: true
|
# require_auth: true
|
||||||
cache_enabled: false
|
# cache_enabled: false
|
||||||
enable_database: true
|
# enable_database: true
|
||||||
cache_ttl: 0
|
# cache_ttl: 0
|
||||||
|
|
||||||
update:
|
# update:
|
||||||
methods: ["PUT"]
|
# methods: ["PUT"]
|
||||||
path: "/:id"
|
# path: "/:id"
|
||||||
put_routes: "/:id"
|
# put_routes: "/:id"
|
||||||
put_path: "/:id"
|
# put_path: "/:id"
|
||||||
model: "UserUpdateRequest"
|
# model: "UserUpdateRequest"
|
||||||
response_model: "UserUpdateResponse"
|
# response_model: "UserUpdateResponse"
|
||||||
request_model: "UserUpdateRequest"
|
# request_model: "UserUpdateRequest"
|
||||||
description: "Update user"
|
# description: "Update user"
|
||||||
summary: "Update User"
|
# summary: "Update User"
|
||||||
tags: ["User"]
|
# tags: ["User"]
|
||||||
require_auth: true
|
# require_auth: true
|
||||||
cache_enabled: false
|
# cache_enabled: false
|
||||||
enable_database: true
|
# enable_database: true
|
||||||
cache_ttl: 0
|
# cache_ttl: 0
|
||||||
|
|
||||||
delete:
|
# delete:
|
||||||
methods: ["DELETE"]
|
# methods: ["DELETE"]
|
||||||
path: "/:id"
|
# path: "/:id"
|
||||||
delete_routes: "/:id"
|
# delete_routes: "/:id"
|
||||||
delete_path: "/:id"
|
# delete_path: "/:id"
|
||||||
model: "User"
|
# model: "User"
|
||||||
response_model: "UserDeleteResponse"
|
# response_model: "UserDeleteResponse"
|
||||||
description: "Delete user"
|
# description: "Delete user"
|
||||||
summary: "Delete User"
|
# summary: "Delete User"
|
||||||
tags: ["User"]
|
# tags: ["User"]
|
||||||
require_auth: true
|
# require_auth: true
|
||||||
cache_enabled: false
|
# cache_enabled: false
|
||||||
enable_database: true
|
# enable_database: true
|
||||||
cache_ttl: 0
|
# cache_ttl: 0
|
||||||
Reference in New Issue
Block a user