279 lines
6.1 KiB
Go
279 lines
6.1 KiB
Go
package aplicare
|
|
|
|
import (
|
|
"api-service/internal/config"
|
|
"api-service/internal/database"
|
|
"api-service/pkg/logger"
|
|
"context"
|
|
"encoding/json"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-playground/validator/v10"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type AplicaresHandler struct {
|
|
syncer *Syncer
|
|
simrs *SimrsDB
|
|
validator *validator.Validate
|
|
logger logger.Logger
|
|
cfg *config.Config
|
|
once sync.Once
|
|
interval time.Duration
|
|
}
|
|
|
|
type AplicaresHandlerConfig struct {
|
|
Config *config.Config
|
|
Logger logger.Logger
|
|
Validator *validator.Validate
|
|
}
|
|
|
|
func NewAplicaresHandler(cfg AplicaresHandlerConfig) *AplicaresHandler {
|
|
statePath := os.Getenv("APLICARES_STATE_PATH")
|
|
if statePath == "" {
|
|
statePath = "./data/state.json"
|
|
}
|
|
|
|
interval, err := time.ParseDuration(os.Getenv("APLICARES_SYNC_INTERVAL"))
|
|
if err != nil || interval <= 0 {
|
|
interval = 5 * time.Minute
|
|
}
|
|
|
|
dryRun := os.Getenv("APLICARES_DRY_RUN") == "true"
|
|
_ = os.MkdirAll("./data", 0755)
|
|
|
|
db := database.New(cfg.Config)
|
|
simrs := NewSimrsDB(db)
|
|
syncer := NewSyncer(simrs, cfg.Config, statePath, dryRun)
|
|
|
|
h := &AplicaresHandler{
|
|
syncer: syncer,
|
|
simrs: simrs,
|
|
validator: cfg.Validator,
|
|
logger: cfg.Logger,
|
|
cfg: cfg.Config,
|
|
interval: interval,
|
|
}
|
|
|
|
if dryRun {
|
|
h.logger.Info("=== APLICARES DRY RUN — tidak kirim ke BPJS ===", nil)
|
|
} else {
|
|
h.logger.Info("=== APLICARES LIVE MODE ===", nil)
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
// =============================================
|
|
// SCHEDULER
|
|
// =============================================
|
|
|
|
func (h *AplicaresHandler) StartScheduler(ctx context.Context) {
|
|
h.once.Do(func() {
|
|
go h.runScheduler(ctx)
|
|
})
|
|
}
|
|
|
|
func (h *AplicaresHandler) runScheduler(ctx context.Context) {
|
|
h.logger.Info("Scheduler started", map[string]interface{}{
|
|
"interval": h.interval.String(),
|
|
})
|
|
|
|
// Langsung sync sekali saat startup
|
|
h.runOnce(ctx)
|
|
|
|
ticker := time.NewTicker(h.interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
h.runOnce(ctx)
|
|
case <-ctx.Done():
|
|
h.logger.Info("Scheduler stopped", nil)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *AplicaresHandler) runOnce(ctx context.Context) {
|
|
result, err := h.syncer.Sync(ctx)
|
|
if err != nil {
|
|
h.logger.Errorf("Sync error: %v", err)
|
|
return
|
|
}
|
|
h.logger.Info("Sync selesai", map[string]interface{}{
|
|
"total": result.TotalRooms,
|
|
"changed": result.Changed,
|
|
"posted": result.Posted,
|
|
"dry_run": result.DryRun,
|
|
"errors": len(result.Errors),
|
|
})
|
|
}
|
|
|
|
// =============================================
|
|
// HTTP HANDLERS
|
|
// =============================================
|
|
|
|
// GetBeds — GET /api/v1/aplicares/beds
|
|
func (h *AplicaresHandler) GetBeds(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
ruangans, err := h.simrs.GetRuangan(ctx)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
detailMap, err := h.simrs.GetBedDetails(ctx)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
beds := buildBedData(ruangans, detailMap)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"total": len(beds),
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
"data": beds,
|
|
})
|
|
}
|
|
|
|
func (h *AplicaresHandler) GetState(c *gin.Context) {
|
|
statePath := os.Getenv("APLICARES_STATE_PATH")
|
|
if statePath == "" {
|
|
statePath = "./data/state.json"
|
|
}
|
|
|
|
state, err := LoadState(statePath)
|
|
if err != nil || state == nil {
|
|
c.JSON(http.StatusOK, gin.H{"message": "belum ada state", "data": nil})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, state)
|
|
}
|
|
|
|
func (h *AplicaresHandler) TriggerSync(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second)
|
|
defer cancel()
|
|
|
|
result, err := h.syncer.Sync(ctx)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// CheckBPJS — GET /api/v1/aplicares/check-bpjs
|
|
func (h *AplicaresHandler) CheckBPJS(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
bpjs := NewBpjsClient(h.cfg.Bpjs)
|
|
start := time.Now()
|
|
kamars, err := bpjs.BacaKamar(ctx, 1, 60)
|
|
elapsed := time.Since(start).Milliseconds()
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "gagal",
|
|
"keterangan": "tidak bisa konek ke BPJS",
|
|
"error": err.Error(),
|
|
"response_ms": elapsed,
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "ok",
|
|
"keterangan": "BPJS bisa diakses",
|
|
"total_kamar": len(kamars),
|
|
"sample": kamars,
|
|
"response_ms": elapsed,
|
|
})
|
|
}
|
|
|
|
func (h *AplicaresHandler) GetRefKelas(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
bpjs := NewBpjsClient(h.cfg.Bpjs)
|
|
start := time.Now()
|
|
kelas, err := bpjs.GetRefKelas(ctx)
|
|
elapsed := time.Since(start).Milliseconds()
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "gagal",
|
|
"error": err.Error(),
|
|
"response_ms": elapsed,
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "ok",
|
|
"total": len(kelas),
|
|
"data": kelas,
|
|
"response_ms": elapsed,
|
|
})
|
|
}
|
|
|
|
func (h *AplicaresHandler) GetSyncLogs(c *gin.Context) {
|
|
content, err := os.ReadFile("./logs/sync.log")
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "belum ada log",
|
|
"data": []interface{}{},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Parse JSON lines
|
|
lines := splitLines(string(content))
|
|
var logs []interface{}
|
|
for _, line := range lines {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
var entry interface{}
|
|
if err := json.Unmarshal([]byte(line), &entry); err == nil {
|
|
logs = append(logs, entry)
|
|
}
|
|
}
|
|
|
|
// Ambil 100 terakhir
|
|
if len(logs) > 100 {
|
|
logs = logs[len(logs)-100:]
|
|
}
|
|
|
|
// Balik urutan — terbaru di atas
|
|
for i, j := 0, len(logs)-1; i < j; i, j = i+1, j-1 {
|
|
logs[i], logs[j] = logs[j], logs[i]
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"total": len(logs),
|
|
"data": logs,
|
|
})
|
|
}
|
|
|
|
func splitLines(s string) []string {
|
|
var lines []string
|
|
start := 0
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == '\n' {
|
|
lines = append(lines, s[start:i])
|
|
start = i + 1
|
|
}
|
|
}
|
|
if start < len(s) {
|
|
lines = append(lines, s[start:])
|
|
}
|
|
return lines
|
|
}
|