first commit
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package antrol
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
antrolService "service/internal/bpjs/antrol/reference"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AntrolHandler struct {
|
||||
service antrolService.Service
|
||||
}
|
||||
|
||||
func NewAntrolHandler(service antrolService.Service) *AntrolHandler {
|
||||
return &AntrolHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *AntrolHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/bpjs/antrol/reference")
|
||||
{
|
||||
group.GET("/poli", h.GetRefPoli)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AntrolHandler) GetRefPoli(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
res, err := h.service.GetRefPoli(ctx)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved BPJS Antrol Poli reference", res)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package aplicare
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
bedService "service/internal/bpjs/aplicare/bed"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BedHandler struct {
|
||||
service bedService.Service
|
||||
}
|
||||
|
||||
func NewBedHandler(service bedService.Service) *BedHandler {
|
||||
return &BedHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *BedHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/bpjs/aplicare/bed")
|
||||
{
|
||||
group.GET("/:kdppk/:start/:limit", h.GetBedList)
|
||||
group.POST("/:kdppk", h.CreateBed)
|
||||
group.PUT("/:kdppk", h.UpdateBed)
|
||||
group.DELETE("/:kdppk", h.DeleteBed)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BedHandler) GetBedList(c *gin.Context) {
|
||||
kdPpk := c.Param("kdppk")
|
||||
start, _ := strconv.Atoi(c.Param("start"))
|
||||
limit, _ := strconv.Atoi(c.Param("limit"))
|
||||
|
||||
ctx := c.Request.Context()
|
||||
res, err := h.service.GetBedList(ctx, kdPpk, start, limit)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved BPJS Aplicares bed list", res)
|
||||
}
|
||||
|
||||
func (h *BedHandler) CreateBed(c *gin.Context) {
|
||||
kdPpk := c.Param("kdppk")
|
||||
var req bedService.BedData
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
if err := h.service.CreateBed(ctx, kdPpk, req); err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "Successfully created BPJS Aplicares bed data", nil)
|
||||
}
|
||||
|
||||
func (h *BedHandler) UpdateBed(c *gin.Context) {
|
||||
kdPpk := c.Param("kdppk")
|
||||
var req bedService.BedData
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
if err := h.service.UpdateBed(ctx, kdPpk, req); err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully updated BPJS Aplicares bed data", nil)
|
||||
}
|
||||
|
||||
func (h *BedHandler) DeleteBed(c *gin.Context) {
|
||||
kdPpk := c.Param("kdppk")
|
||||
var req bedService.BedDeletePayload
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
if err := h.service.DeleteBed(ctx, kdPpk, req); err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully deleted BPJS Aplicares bed data", nil)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package apotek
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
dphoService "service/internal/bpjs/apotek/reference/dpho"
|
||||
poliService "service/internal/bpjs/apotek/reference/poli"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ReferenceHandler struct {
|
||||
dphoService dphoService.Service
|
||||
poliService poliService.Service
|
||||
}
|
||||
|
||||
func NewReferenceHandler(dpho dphoService.Service, poli poliService.Service) *ReferenceHandler {
|
||||
return &ReferenceHandler{
|
||||
dphoService: dpho,
|
||||
poliService: poli,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ReferenceHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/bpjs/apotek/reference")
|
||||
{
|
||||
group.GET("/dpho", h.GetDPHO)
|
||||
group.GET("/poli/:param", h.GetPoli)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ReferenceHandler) GetDPHO(c *gin.Context) {
|
||||
res, err := h.dphoService.GetDPHO(c.Request.Context())
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved BPJS Apotek DPHO reference", res)
|
||||
}
|
||||
|
||||
func (h *ReferenceHandler) GetPoli(c *gin.Context) {
|
||||
param := c.Param("param")
|
||||
res, err := h.poliService.GetPoli(c.Request.Context(), param)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved BPJS Apotek Poli reference", res)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package vclaim
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
pesertaService "service/internal/bpjs/vclaim/peserta"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PesertaHandler struct {
|
||||
service pesertaService.Service
|
||||
}
|
||||
|
||||
func NewPesertaHandler(service pesertaService.Service) *PesertaHandler {
|
||||
return &PesertaHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *PesertaHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/bpjs/vclaim/peserta")
|
||||
{
|
||||
group.GET("/nik/:nik", h.GetPesertaByNIK)
|
||||
group.GET("/nokartu/:nokartu", h.GetPesertaByNoKartu)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PesertaHandler) GetPesertaByNIK(c *gin.Context) {
|
||||
nik := c.Param("nik")
|
||||
tglSEP := c.Query("tgl_sep")
|
||||
if tglSEP == "" {
|
||||
tglSEP = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
res, err := h.service.GetPesertaByNIK(ctx, nik, tglSEP)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved participant data by NIK", res)
|
||||
}
|
||||
|
||||
func (h *PesertaHandler) GetPesertaByNoKartu(c *gin.Context) {
|
||||
noKartu := c.Param("nokartu")
|
||||
tglSEP := c.Query("tgl_sep")
|
||||
if tglSEP == "" {
|
||||
tglSEP = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
res, err := h.service.GetPesertaByNoKartu(ctx, noKartu, tglSEP)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved participant data by card number", res)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package vclaim
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
sepService "service/internal/bpjs/vclaim/sep"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SepHandler menangani semua request HTTP terkait VClaim.
|
||||
type SepHandler struct {
|
||||
sepService sepService.Service
|
||||
}
|
||||
|
||||
// NewSepHandler membuat instance SepHandler baru.
|
||||
func NewSepHandler(sepService sepService.Service) *SepHandler {
|
||||
return &SepHandler{
|
||||
sepService: sepService,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes mendaftarkan semua rute untuk BPJS VClaim.
|
||||
func (h *SepHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
bpjsGroup := router.Group("/bpjs/vclaim")
|
||||
{
|
||||
sepGroup := bpjsGroup.Group("/sep")
|
||||
{
|
||||
sepGroup.POST("", h.CreateSEP)
|
||||
sepGroup.PUT("", h.UpdateSEP)
|
||||
// BPJS API untuk delete menggunakan method POST, tapi kita ekspos sebagai DELETE untuk konsistensi RESTful.
|
||||
sepGroup.DELETE("", h.DeleteSEP)
|
||||
sepGroup.GET("/:nosep", h.GetSEPDetail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSEP menangani request untuk membuat SEP baru.
|
||||
func (h *SepHandler) CreateSEP(c *gin.Context) {
|
||||
var req sepService.CreateSEPRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.sepService.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "Successfully created SEP", result)
|
||||
}
|
||||
|
||||
// UpdateSEP menangani request untuk memperbarui SEP.
|
||||
func (h *SepHandler) UpdateSEP(c *gin.Context) {
|
||||
var req sepService.UpdateSEPRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.sepService.Update(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully updated SEP", result)
|
||||
}
|
||||
|
||||
// GetSEPDetail menangani request untuk mendapatkan detail SEP.
|
||||
func (h *SepHandler) GetSEPDetail(c *gin.Context) {
|
||||
noSEP := c.Param("no_sep")
|
||||
|
||||
result, err := h.sepService.GetDetail(c.Request.Context(), noSEP)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved SEP detail", result)
|
||||
}
|
||||
|
||||
// DeleteSEP menangani request untuk menghapus SEP.
|
||||
func (h *SepHandler) DeleteSEP(c *gin.Context) {
|
||||
var req sepService.DeleteSEPRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body for SEP deletion", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.sepService.Delete(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully deleted SEP", result)
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
authService "service/internal/auth"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
service authService.Service
|
||||
}
|
||||
|
||||
func NewAuthHandler(service authService.Service) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
auth := router.Group("/auth")
|
||||
{
|
||||
auth.POST("/login", h.Login)
|
||||
auth.POST("/register", h.Register)
|
||||
auth.POST("/refresh", h.RefreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterProtectedRoutes mendaftarkan endpoint auth yang membutuhkan token
|
||||
func (h *AuthHandler) RegisterProtectedRoutes(router *gin.RouterGroup) {
|
||||
auth := router.Group("/auth")
|
||||
{
|
||||
auth.POST("/logout", h.Logout)
|
||||
auth.GET("/info", h.TokenInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Login godoc
|
||||
//
|
||||
// @Summary Login user
|
||||
// @Description Authenticate user and return JWT & Refresh tokens
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body authService.LoginRequest true "Login credentials"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /auth/login [post]
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req authService.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Pemanggilan service secara by-value
|
||||
resp, err := h.service.Login(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Login successful", resp)
|
||||
}
|
||||
|
||||
// Register godoc
|
||||
//
|
||||
// @Summary Register user
|
||||
// @Description Register a new user
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body authService.RegisterRequest true "Registration details"
|
||||
// @Success 201 {object} response.Response
|
||||
// @Router /auth/register [post]
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req authService.RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.Register(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "User registered successfully", resp)
|
||||
}
|
||||
|
||||
// RefreshToken godoc
|
||||
//
|
||||
// @Summary Refresh Token
|
||||
// @Description Refresh expired access token using refresh token
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body authService.RefreshTokenRequest true "Refresh token payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /auth/refresh [post]
|
||||
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
||||
var req authService.RefreshTokenRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request payload", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.RefreshToken(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Token refreshed successfully", resp)
|
||||
}
|
||||
|
||||
// Logout godoc
|
||||
//
|
||||
// @Summary Logout user
|
||||
// @Description Logout current user and blacklist the token
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /auth/logout [post]
|
||||
func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
response.Error(c, http.StatusBadRequest, "Token required", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(token) > 7 && token[:7] == "Bearer " {
|
||||
token = token[7:]
|
||||
}
|
||||
|
||||
err := h.service.Logout(c.Request.Context(), token)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Logged out successfully", nil)
|
||||
}
|
||||
|
||||
// TokenInfo godoc
|
||||
//
|
||||
// @Summary Get Token Info
|
||||
// @Description Retrieve information about the currently active token/user
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /auth/info [get]
|
||||
func (h *AuthHandler) TokenInfo(c *gin.Context) {
|
||||
// Data ini otomatis di-set oleh provider.go (UnifiedAuthMiddleware)
|
||||
authProvider := c.GetString("auth_provider")
|
||||
userID := c.GetString("user_id")
|
||||
username := c.GetString("username")
|
||||
email := c.GetString("email")
|
||||
role := c.GetString("role")
|
||||
name := c.GetString("name")
|
||||
|
||||
data := gin.H{
|
||||
"auth_provider": authProvider, // Akan bernilai: "jwt", "keycloak", atau "static"
|
||||
"user_id": userID,
|
||||
"username": username,
|
||||
"email": email,
|
||||
"name": name,
|
||||
"role": role,
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Current active token information", data)
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"service/internal/infrastructure/cache"
|
||||
"service/internal/infrastructure/config"
|
||||
"service/internal/infrastructure/database"
|
||||
"service/internal/interfaces/minio"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// HealthHandler handles health check endpoints
|
||||
type HealthHandler struct {
|
||||
db *gorm.DB
|
||||
redisClient *redis.Client
|
||||
config *config.Config
|
||||
cacheManager *cache.Manager // Alternatif untuk Redis client
|
||||
dbManager database.Service
|
||||
}
|
||||
|
||||
// NewHealthHandlerWithCache creates a new health handler with cache manager
|
||||
func NewHealthHandlerWithCache(db *gorm.DB, cacheManager *cache.Manager, config *config.Config, dbManager database.Service) *HealthHandler {
|
||||
handler := &HealthHandler{
|
||||
db: db,
|
||||
config: config,
|
||||
cacheManager: cacheManager,
|
||||
dbManager: dbManager,
|
||||
}
|
||||
|
||||
// Coba dapatkan Redis client dari cache manager
|
||||
if cacheManager != nil {
|
||||
if redisClientInterface := cacheManager.GetRedisClient(); redisClientInterface != nil {
|
||||
if client, ok := redisClientInterface.(*redis.Client); ok {
|
||||
handler.redisClient = client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
// HealthCheckComplete performs comprehensive health check
|
||||
func (h *HealthHandler) HealthCheckComplete(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
|
||||
health := gin.H{
|
||||
"status": "healthy",
|
||||
"timestamp": time.Now().UTC(),
|
||||
"uptime": time.Since(startTime).Milliseconds(),
|
||||
"version": "1.0.0",
|
||||
"service": "service",
|
||||
}
|
||||
|
||||
// Check database
|
||||
dbStatus := h.checkDatabase()
|
||||
health["database"] = dbStatus
|
||||
|
||||
// Check cache
|
||||
cacheStatus := h.checkCache()
|
||||
health["cache"] = cacheStatus
|
||||
|
||||
// Check external services
|
||||
externalStatus := h.checkExternalServices()
|
||||
health["external_services"] = externalStatus
|
||||
|
||||
// Check Minio
|
||||
minioStatus := h.checkMinio()
|
||||
health["minio"] = minioStatus
|
||||
|
||||
// Determine overall status
|
||||
if dbStatus["status"] != "UP" || cacheStatus["status"] != "UP" || (minioStatus["status"] != "UP" && minioStatus["status"] != "DISABLED") {
|
||||
health["status"] = "DOWN"
|
||||
c.JSON(http.StatusServiceUnavailable, health)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, health)
|
||||
}
|
||||
|
||||
// HealthCheckDatabase checks database connectivity
|
||||
func (h *HealthHandler) HealthCheckDatabase(c *gin.Context) {
|
||||
status := h.checkDatabase()
|
||||
|
||||
if status["status"] != "UP" {
|
||||
c.JSON(http.StatusServiceUnavailable, status)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, status)
|
||||
}
|
||||
|
||||
// HealthCheckCache checks cache connectivity
|
||||
func (h *HealthHandler) HealthCheckCache(c *gin.Context) {
|
||||
status := h.checkCache()
|
||||
|
||||
if status["status"] != "UP" {
|
||||
c.JSON(http.StatusServiceUnavailable, status)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, status)
|
||||
}
|
||||
|
||||
// HealthCheckExternal checks external service connectivity
|
||||
func (h *HealthHandler) HealthCheckExternal(c *gin.Context) {
|
||||
status := h.checkExternalServices()
|
||||
|
||||
// Hanya return 503 (Unavailable) jika status benar-benar DOWN, bukan saat sekadar DEGRADED.
|
||||
if status["status"] == "DOWN" {
|
||||
c.JSON(http.StatusServiceUnavailable, status)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, status)
|
||||
}
|
||||
|
||||
// HealthCheckMinio checks Minio Object Storage connectivity
|
||||
func (h *HealthHandler) HealthCheckMinio(c *gin.Context) {
|
||||
status := h.checkMinio()
|
||||
|
||||
if status["status"] == "DOWN" {
|
||||
c.JSON(http.StatusServiceUnavailable, status)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, status)
|
||||
}
|
||||
|
||||
// TestUploadMinio is a testing endpoint to upload a file to Minio
|
||||
func (h *HealthHandler) TestUploadMinio(c *gin.Context) {
|
||||
if minio.I == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Minio client is not initialized or disconnected"})
|
||||
return
|
||||
}
|
||||
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "File is required (form-data key: 'file')"})
|
||||
return
|
||||
}
|
||||
|
||||
// Gunakan bucket dari request body (jika ada), atau default ke config/nama statis
|
||||
bucketName := c.DefaultPostForm("bucket", "dev-test")
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// Ensure bucket exists
|
||||
exists, err := minio.I.BucketExists(ctx, bucketName)
|
||||
if err == nil && !exists {
|
||||
err = minio.I.MakeBucket(ctx, bucketName, miniogo.MakeBucketOptions{Region: h.config.Minio.Region})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create bucket: " + err.Error()})
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check bucket status: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open file: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
objectName := fmt.Sprintf("%d-%s", time.Now().Unix(), file.Filename)
|
||||
info, err := minio.I.PutObject(ctx, bucketName, objectName, src, file.Size, miniogo.PutObjectOptions{
|
||||
ContentType: file.Header.Get("Content-Type"),
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to upload to Minio: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "File uploaded successfully",
|
||||
"data": info,
|
||||
})
|
||||
}
|
||||
|
||||
// checkDatabase checks database health
|
||||
func (h *HealthHandler) checkDatabase() gin.H {
|
||||
if h.dbManager == nil {
|
||||
return gin.H{
|
||||
"status": "UNKNOWN",
|
||||
"error": "Database manager not initialized",
|
||||
}
|
||||
}
|
||||
|
||||
allDbInfo := h.dbManager.GetAllDatabasesInfo()
|
||||
overallStatus := "UP"
|
||||
|
||||
// Iterate through the map and check each database
|
||||
for name, info := range allDbInfo {
|
||||
dbInfo, ok := info.(gin.H)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Standardize the status to "UP" if it's "connected" or "healthy"
|
||||
if status, ok := dbInfo["status"].(string); ok {
|
||||
if status == "connected" || status == "healthy" || status == "UP" {
|
||||
dbInfo["status"] = "UP"
|
||||
} else {
|
||||
overallStatus = "DOWN"
|
||||
}
|
||||
}
|
||||
allDbInfo[name] = dbInfo // Update the map with the checked info
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"status": overallStatus,
|
||||
"components": allDbInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// checkCache checks Redis/cache health
|
||||
func (h *HealthHandler) checkCache() gin.H {
|
||||
if h.cacheManager == nil {
|
||||
return gin.H{"status": "UNKNOWN", "error": "Cache manager not initialized"}
|
||||
}
|
||||
|
||||
if !h.config.Cache.Enabled {
|
||||
return gin.H{"status": "UP", "details": gin.H{"provider": "noop", "message": "Cache is disabled"}}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
start := time.Now()
|
||||
err := h.cacheManager.Health(ctx)
|
||||
latency := time.Since(start)
|
||||
|
||||
details := gin.H{
|
||||
"provider": "redis",
|
||||
"latency": latency.String(),
|
||||
"latency_ms": latency.Milliseconds(),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
details["error"] = err.Error()
|
||||
return gin.H{"status": "DOWN", "details": details}
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"status": "UP",
|
||||
"details": details,
|
||||
}
|
||||
}
|
||||
|
||||
// checkMinio checks Minio Object Storage health
|
||||
func (h *HealthHandler) checkMinio() gin.H {
|
||||
if h.config == nil || h.config.Minio.Endpoint == "" {
|
||||
return gin.H{"status": "DISABLED", "message": "Minio is not configured in .env"}
|
||||
}
|
||||
|
||||
if minio.I == nil {
|
||||
return gin.H{"status": "DOWN", "message": "Minio global client is nil"}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
start := time.Now()
|
||||
// Panggilan ringan API minio untuk memastikan server hidup & kredensial benar
|
||||
_, err := minio.I.ListBuckets(ctx)
|
||||
latency := time.Since(start)
|
||||
|
||||
details := gin.H{
|
||||
"endpoint": h.config.Minio.Endpoint,
|
||||
"latency_ms": latency.Milliseconds(),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
details["error"] = err.Error()
|
||||
return gin.H{"status": "DOWN", "details": details}
|
||||
}
|
||||
|
||||
return gin.H{"status": "UP", "details": details}
|
||||
}
|
||||
|
||||
// checkExternalServices checks external service health
|
||||
func (h *HealthHandler) checkExternalServices() gin.H {
|
||||
status := gin.H{
|
||||
"status": "UP",
|
||||
"message": "All configured external services are reachable",
|
||||
}
|
||||
|
||||
hasDegraded := false
|
||||
hasDown := false
|
||||
downServices := []string{}
|
||||
degradedServices := []string{}
|
||||
activeServices := 0
|
||||
|
||||
// Check BPJS service if configured
|
||||
if h.config != nil {
|
||||
bpjsStatus := h.checkBPJSService()
|
||||
status["bpjs"] = bpjsStatus
|
||||
|
||||
if s, ok := bpjsStatus["status"].(string); ok && s != "DISABLED" {
|
||||
activeServices++
|
||||
if s == "DOWN" {
|
||||
hasDown = true
|
||||
downServices = append(downServices, "BPJS")
|
||||
} else if s == "DEGRADED" {
|
||||
hasDegraded = true
|
||||
degradedServices = append(degradedServices, "BPJS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check SatuSehat service if configured
|
||||
if h.config != nil {
|
||||
satuSehatStatus := h.checkSatuSehatService()
|
||||
status["satu_sehat"] = satuSehatStatus
|
||||
|
||||
if s, ok := satuSehatStatus["status"].(string); ok && s != "DISABLED" {
|
||||
activeServices++
|
||||
if s == "DOWN" {
|
||||
hasDown = true
|
||||
downServices = append(downServices, "SatuSehat")
|
||||
} else if s == "DEGRADED" {
|
||||
hasDegraded = true
|
||||
degradedServices = append(degradedServices, "SatuSehat")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if activeServices == 0 {
|
||||
status["status"] = "UP"
|
||||
status["message"] = "All external services are disabled"
|
||||
} else if hasDown {
|
||||
status["status"] = "DOWN"
|
||||
status["message"] = fmt.Sprintf("Some external services are unreachable: %v", downServices)
|
||||
} else if hasDegraded {
|
||||
status["status"] = "DEGRADED"
|
||||
status["message"] = fmt.Sprintf("Some external services are degraded: %v", degradedServices)
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// checkBPJSService checks BPJS VClaim service
|
||||
func (h *HealthHandler) checkBPJSService() gin.H {
|
||||
if !h.config.Bpjs.Enabled {
|
||||
return gin.H{
|
||||
"status": "DISABLED",
|
||||
"message": "BPJS integration is disabled in configuration",
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", h.config.Bpjs.BaseURL, nil)
|
||||
if err != nil {
|
||||
return gin.H{
|
||||
"status": "DOWN",
|
||||
"message": "Failed to create BPJS request: " + err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return gin.H{
|
||||
"status": "DOWN",
|
||||
"message": "BPJS service unreachable: " + err.Error(),
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
status := "UP"
|
||||
message := "BPJS service is reachable"
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
message = "BPJS service is reachable and responding normally"
|
||||
case http.StatusUnauthorized:
|
||||
message = "BPJS service is reachable (Authentication required/Invalid Signature)"
|
||||
case http.StatusForbidden:
|
||||
status = "DEGRADED"
|
||||
message = "BPJS service is reachable, but access is forbidden (Check IP Whitelisting or Credentials)"
|
||||
case http.StatusNotFound:
|
||||
message = "BPJS service is reachable (Base URL responded with 404, server is UP)"
|
||||
default:
|
||||
if resp.StatusCode >= 500 {
|
||||
status = "DOWN"
|
||||
message = fmt.Sprintf("BPJS service returned server error: %s", resp.Status)
|
||||
} else {
|
||||
message = fmt.Sprintf("BPJS service is reachable (Status: %s)", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"status": status,
|
||||
"message": message,
|
||||
"status_code": resp.StatusCode,
|
||||
}
|
||||
}
|
||||
|
||||
// checkSatuSehatService checks SatuSehat FHIR service
|
||||
func (h *HealthHandler) checkSatuSehatService() gin.H {
|
||||
if !h.config.SatuSehat.Enabled {
|
||||
return gin.H{
|
||||
"status": "DISABLED",
|
||||
"message": "SatuSehat integration is disabled in configuration",
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 2 * time.Second, // Dipercepat agar health check tidak blocking terlalu lama
|
||||
}
|
||||
|
||||
checkURL := func(url string) (string, string, int) {
|
||||
if url == "" {
|
||||
return "DISABLED", "URL not configured", 0
|
||||
}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "DOWN", "Request creation failed: " + err.Error(), 0
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "DOWN", "Unreachable: " + err.Error(), 0
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return "UP", fmt.Sprintf("Reachable (Status: %s)", resp.Status), resp.StatusCode
|
||||
}
|
||||
|
||||
authStatus, authMsg, authCode := checkURL(h.config.SatuSehat.AuthURL)
|
||||
baseStatus, baseMsg, baseCode := checkURL(h.config.SatuSehat.BaseURL)
|
||||
consentStatus, consentMsg, _ := checkURL(h.config.SatuSehat.ConsentURL)
|
||||
kfaStatus, kfaMsg, _ := checkURL(h.config.SatuSehat.KFAURL)
|
||||
|
||||
overallStatus := "UP"
|
||||
overallMessage := "SatuSehat services are reachable"
|
||||
|
||||
// Evaluasi status keseluruhan berdasarkan Base URL & Auth URL
|
||||
if baseStatus == "DOWN" || authStatus == "DOWN" {
|
||||
overallStatus = "DOWN"
|
||||
overallMessage = "One or more critical SatuSehat services are unreachable"
|
||||
} else if baseCode == http.StatusForbidden || authCode == http.StatusForbidden {
|
||||
overallStatus = "DEGRADED"
|
||||
overallMessage = "Services are reachable, but access is forbidden (Check IP/Permissions)"
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"status": overallStatus,
|
||||
"message": overallMessage,
|
||||
"details": gin.H{
|
||||
"org_id": h.config.SatuSehat.OrgID,
|
||||
"fasyankes_id": h.config.SatuSehat.FasyakesID,
|
||||
"endpoints": gin.H{
|
||||
"auth": gin.H{"url": h.config.SatuSehat.AuthURL, "status": authStatus, "message": authMsg, "status_code": authCode},
|
||||
"fhir_base": gin.H{"url": h.config.SatuSehat.BaseURL, "status": baseStatus, "message": baseMsg, "status_code": baseCode},
|
||||
"consent": gin.H{"url": h.config.SatuSehat.ConsentURL, "status": consentStatus, "message": consentMsg},
|
||||
"kfa": gin.H{"url": h.config.SatuSehat.KFAURL, "status": kfaStatus, "message": kfaMsg},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
masterService "service/internal/master/role/master"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/logger"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type RoleMasterHandler struct {
|
||||
service masterService.Service
|
||||
}
|
||||
|
||||
func NewRoleMasterHandler(service masterService.Service) *RoleMasterHandler {
|
||||
return &RoleMasterHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *RoleMasterHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/roles/master")
|
||||
{
|
||||
group.GET("", h.GetList)
|
||||
group.GET("/search", h.Search)
|
||||
group.GET("/:id", h.GetDetail)
|
||||
group.POST("", h.Create)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.DELETE("/:id", h.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
// GetList godoc
|
||||
//
|
||||
// @Summary Get list of Role
|
||||
// @Description Retrieve a paginated list of RoleMaster
|
||||
// @Tags roles
|
||||
// @Produce json
|
||||
// @Param limit query int false "Number of items per page" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Param sort query string false "Sort fields (e.g. +name,-created_at)"
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/master [get]
|
||||
func (h *RoleMasterHandler) GetList(c *gin.Context) {
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
limit, _ = strconv.Atoi(limitStr)
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset > 0 && limit > 0 {
|
||||
offset = (offset - 1) * limit
|
||||
}
|
||||
}
|
||||
|
||||
activeParam := c.Query("active")
|
||||
var activeFilter *bool // default nil (no filter / ambil semua)
|
||||
if activeParam != "" {
|
||||
isActive := activeParam == "true" || activeParam == "1"
|
||||
activeFilter = &isActive
|
||||
}
|
||||
|
||||
// Parse parameter sort (format: sort=column1,-column2,+column3)
|
||||
// -column untuk DESC, +column atau column untuk ASC
|
||||
var sorts []string
|
||||
if sortParam := c.Query("sort"); sortParam != "" {
|
||||
sorts = strings.Split(sortParam, ",")
|
||||
// Validasi dan bersihkan sort parameters
|
||||
for i, sort := range sorts {
|
||||
sorts[i] = strings.TrimSpace(sort)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetList(ctx, limit, offset, sorts, activeFilter)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
data := result["data"]
|
||||
total := result["total"].(int64)
|
||||
limitVal := result["limit"].(int)
|
||||
offsetVal := result["offset"].(int)
|
||||
|
||||
page := 1
|
||||
if limitVal > 0 {
|
||||
page = (offsetVal / limitVal) + 1
|
||||
}
|
||||
totalPages := 0
|
||||
if limitVal > 0 {
|
||||
totalPages = int(math.Ceil(float64(total) / float64(limitVal)))
|
||||
}
|
||||
|
||||
meta := response.Meta{Page: page, Limit: limitVal, Total: int(total), TotalPages: totalPages}
|
||||
response.Paginated(c, http.StatusOK, "Successfully retrieved RolPages list", data, meta)
|
||||
}
|
||||
|
||||
// GetDetail godoc
|
||||
//
|
||||
// @Summary Get RoleMaster detail
|
||||
// @Description Retrieve detailed information about a specific RoleMaster
|
||||
// @Tags roles
|
||||
// @Produce json
|
||||
// @Param id path int true "RoleMaster ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/master/{id} [get]
|
||||
func (h *RoleMasterHandler) GetDetail(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetDetail(ctx, id)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved RoleMaster detail", result)
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
//
|
||||
// @Summary Search Role
|
||||
// @Description Search RoleMaster records using dynamic filters
|
||||
// @Tags roles
|
||||
// @Produce json
|
||||
// @Param limit query int false "Limit per page" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Param sort query string false "Sort fields (e.g. +name,-created_at)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/master/search [get]
|
||||
func (h *RoleMasterHandler) Search(c *gin.Context) {
|
||||
// Parse Limit & Offset dengan fallback ke page & page_size
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
limit, _ = strconv.Atoi(limitStr)
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset > 0 && limit > 0 {
|
||||
offset = (offset - 1) * limit
|
||||
}
|
||||
}
|
||||
|
||||
// Ambil parameter filter
|
||||
// Ambil parameter filter secara dinamis
|
||||
filters := make(map[string]interface{})
|
||||
|
||||
for key, values := range c.Request.URL.Query() {
|
||||
if key == "page" || key == "limit" || key == "page_size" || key == "offset" || key == "sort" {
|
||||
continue
|
||||
}
|
||||
if len(values) > 0 && values[0] != "" {
|
||||
filters[key] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parameter sort (format: sort=column1,-column2,+column3)
|
||||
// -column untuk DESC, +column atau column untuk ASC
|
||||
var sorts []string
|
||||
if sortParam := c.Query("sort"); sortParam != "" {
|
||||
sorts = strings.Split(sortParam, ",")
|
||||
// Validasi dan bersihkan sort parameters
|
||||
for i, sort := range sorts {
|
||||
sorts[i] = strings.TrimSpace(sort)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
logger.Default().WithContext(ctx).Info("Search request",
|
||||
logger.String("filters", fmt.Sprintf("%v", filters)),
|
||||
logger.String("sorts", fmt.Sprintf("%v", sorts)),
|
||||
logger.Int("offset", offset),
|
||||
logger.Int("limit", limit))
|
||||
|
||||
// Panggil service dengan parameter sort tambahan
|
||||
result, err := h.service.Search(ctx, filters, sorts, limit, offset)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
// Extract data dari map service untuk response format
|
||||
data := result["data"]
|
||||
total := result["total"].(int64)
|
||||
limitVal := result["limit"].(int)
|
||||
offsetVal := result["offset"].(int)
|
||||
|
||||
page := 1
|
||||
if limitVal > 0 {
|
||||
page = (offsetVal / limitVal) + 1
|
||||
}
|
||||
// Hitung total pages
|
||||
totalPages := 0
|
||||
if limitVal > 0 {
|
||||
totalPages = int(math.Ceil(float64(total) / float64(limitVal)))
|
||||
}
|
||||
|
||||
meta := response.Meta{
|
||||
Page: page,
|
||||
Limit: limitVal,
|
||||
Total: int(total),
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
|
||||
response.Paginated(c, http.StatusOK, "Successfully retrieved RoleMaster search results", data, meta)
|
||||
}
|
||||
|
||||
// Create godoc
|
||||
//
|
||||
// @Summary Create new RoleMaster
|
||||
// @Description Create a new RoleMaster record
|
||||
// @Tags roles
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body masterService.RoleMasterRequest true "Payload"
|
||||
// @Success 201 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/master [post]
|
||||
func (h *RoleMasterHandler) Create(c *gin.Context) {
|
||||
var req masterService.RoleMasterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
created, err := h.service.Create(ctx, req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "Successfully created RoleMaster", created)
|
||||
}
|
||||
|
||||
// Update godoc
|
||||
//
|
||||
// @Summary Update an existing RoleMaster
|
||||
// @Description Update details of an existing RoleMaster record by ID
|
||||
// @Tags roles
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "RoleMaster ID"
|
||||
// @Param request body masterService.RoleMasterRequest true "Payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/master/{id} [put]
|
||||
func (h *RoleMasterHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req masterService.RoleMasterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
updated, err := h.service.Update(ctx, id, req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully updated RoleMaster", updated)
|
||||
}
|
||||
|
||||
// Delete godoc
|
||||
//
|
||||
// @Summary Delete a RoleMaster
|
||||
// @Description Delete a RoleMaster record by ID (soft delete)
|
||||
// @Tags roles
|
||||
// @Produce json
|
||||
// @Param id path int true "RoleMaster ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/master/{id} [delete]
|
||||
func (h *RoleMasterHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
if err := h.service.Delete(ctx, id); err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully deleted RoleMaster", nil)
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
roleService "service/internal/master/role/pages"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/logger"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type RolPagesHandler struct {
|
||||
service roleService.Service
|
||||
}
|
||||
|
||||
func NewRolPagesHandler(service roleService.Service) *RolPagesHandler {
|
||||
return &RolPagesHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *RolPagesHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/roles/pages")
|
||||
{
|
||||
group.GET("", h.GetList)
|
||||
group.GET("/tree", h.GetTree)
|
||||
group.GET("/tree/level/:level", h.GetTreeByLevel)
|
||||
group.GET("/search", h.Search)
|
||||
group.GET("/:id", h.GetDetail)
|
||||
group.POST("", h.Create)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.DELETE("/:id", h.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
// GetList godoc
|
||||
//
|
||||
// @Summary Get list of Role Pages
|
||||
// @Description Retrieve a paginated list of Role Pages
|
||||
// @Tags roles-pages
|
||||
// @Produce json
|
||||
// @Param limit query int false "Number of items per page" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Param sort query string false "Sort fields (e.g. +name,-created_at)"
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages [get]
|
||||
func (h *RolPagesHandler) GetList(c *gin.Context) {
|
||||
// Parse Limit & Offset dengan fallback ke page & page_size
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
limit, _ = strconv.Atoi(limitStr)
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset > 0 && limit > 0 {
|
||||
offset = (offset - 1) * limit
|
||||
}
|
||||
}
|
||||
|
||||
activeParam := c.Query("active")
|
||||
var activeFilter *bool // default nil (no filter / ambil semua)
|
||||
if activeParam != "" {
|
||||
isActive := activeParam == "true" || activeParam == "1"
|
||||
activeFilter = &isActive
|
||||
}
|
||||
|
||||
// Parse parameter sort (format: sort=column1,-column2,+column3)
|
||||
// -column untuk DESC, +column atau column untuk ASC
|
||||
var sorts []string
|
||||
if sortParam := c.Query("sort"); sortParam != "" {
|
||||
sorts = strings.Split(sortParam, ",")
|
||||
// Validasi dan bersihkan sort parameters
|
||||
for i, sort := range sorts {
|
||||
sorts[i] = strings.TrimSpace(sort)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetList(ctx, limit, offset, sorts, activeFilter)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
data := result["data"]
|
||||
total := result["total"].(int64)
|
||||
limitVal := result["limit"].(int)
|
||||
offsetVal := result["offset"].(int)
|
||||
|
||||
page := 1
|
||||
if limitVal > 0 {
|
||||
page = (offsetVal / limitVal) + 1
|
||||
}
|
||||
totalPages := 0
|
||||
if limitVal > 0 {
|
||||
totalPages = int(math.Ceil(float64(total) / float64(limitVal)))
|
||||
}
|
||||
|
||||
meta := response.Meta{Page: page, Limit: limitVal, Total: int(total), TotalPages: totalPages}
|
||||
response.Paginated(c, http.StatusOK, "Successfully retrieved RolPages list", data, meta)
|
||||
}
|
||||
|
||||
// GetDetail godoc
|
||||
//
|
||||
// @Summary Get Role Pages detail
|
||||
// @Description Retrieve detailed information about a specific Role Pages
|
||||
// @Tags roles-pages
|
||||
// @Produce json
|
||||
// @Param id path int true "Role Pages ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages/{id} [get]
|
||||
func (h *RolPagesHandler) GetDetail(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetDetail(ctx, id)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved RolPages detail", result)
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
//
|
||||
// @Summary Search Role Pages
|
||||
// @Description Search Role Pages records using dynamic filters
|
||||
// @Tags roles-pages
|
||||
// @Produce json
|
||||
// @Param limit query int false "Limit per page" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Param sort query string false "Sort fields"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages/search [get]
|
||||
func (h *RolPagesHandler) Search(c *gin.Context) {
|
||||
// Parse Limit & Offset dengan fallback ke page & page_size
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
limit, _ = strconv.Atoi(limitStr)
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset > 0 && limit > 0 {
|
||||
offset = (offset - 1) * limit
|
||||
}
|
||||
}
|
||||
|
||||
// Ambil parameter filter secara dinamis
|
||||
filters := make(map[string]interface{})
|
||||
|
||||
for key, values := range c.Request.URL.Query() {
|
||||
if key == "page" || key == "limit" || key == "page_size" || key == "offset" || key == "sort" {
|
||||
continue
|
||||
}
|
||||
if len(values) > 0 && values[0] != "" {
|
||||
filters[key] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parameter sort (format: sort=column1,-column2,+column3)
|
||||
// -column untuk DESC, +column atau column untuk ASC
|
||||
var sorts []string
|
||||
if sortParam := c.Query("sort"); sortParam != "" {
|
||||
sorts = strings.Split(sortParam, ",")
|
||||
// Validasi dan bersihkan sort parameters
|
||||
for i, sort := range sorts {
|
||||
sorts[i] = strings.TrimSpace(sort)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
logger.Default().WithContext(ctx).Info("Search request",
|
||||
logger.String("filters", fmt.Sprintf("%v", filters)),
|
||||
logger.String("sorts", fmt.Sprintf("%v", sorts)),
|
||||
logger.Int("offset", offset),
|
||||
logger.Int("limit", limit))
|
||||
|
||||
// Panggil service dengan parameter sort tambahan
|
||||
result, err := h.service.Search(ctx, filters, sorts, limit, offset)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
// Extract data dari map service untuk response format
|
||||
data := result["data"]
|
||||
total := result["total"].(int64)
|
||||
limitVal := result["limit"].(int)
|
||||
offsetVal := result["offset"].(int)
|
||||
|
||||
page := 1
|
||||
if limitVal > 0 {
|
||||
page = (offsetVal / limitVal) + 1
|
||||
}
|
||||
totalPages := 0
|
||||
if limitVal > 0 {
|
||||
totalPages = int(math.Ceil(float64(total) / float64(limitVal)))
|
||||
}
|
||||
|
||||
meta := response.Meta{
|
||||
Page: page,
|
||||
Limit: limitVal,
|
||||
Total: int(total),
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
|
||||
response.Paginated(c, http.StatusOK, "Successfully retrieved RolPages search results", data, meta)
|
||||
}
|
||||
|
||||
// Create godoc
|
||||
//
|
||||
// @Summary Create new Role Pages
|
||||
// @Description Create a new Role Pages record
|
||||
// @Tags roles-pages
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body roleService.RolPagesRequest true "Payload"
|
||||
// @Success 201 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages [post]
|
||||
func (h *RolPagesHandler) Create(c *gin.Context) {
|
||||
var req roleService.RolPagesRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
created, err := h.service.Create(ctx, req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "Successfully created RolPages", created)
|
||||
}
|
||||
|
||||
// Update godoc
|
||||
//
|
||||
// @Summary Update an existing Role Pages
|
||||
// @Description Update details of an existing Role Pages record by ID
|
||||
// @Tags roles-pages
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Role Pages ID"
|
||||
// @Param request body roleService.RolPagesRequest true "Payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages/{id} [put]
|
||||
func (h *RolPagesHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req roleService.RolPagesRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
updated, err := h.service.Update(ctx, id, req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully updated RolPages", updated)
|
||||
}
|
||||
|
||||
// Delete godoc
|
||||
//
|
||||
// @Summary Delete a Role Pages
|
||||
// @Description Delete a Role Pages record by ID
|
||||
// @Tags roles-pages
|
||||
// @Produce json
|
||||
// @Param id path int true "Role Pages ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages/{id} [delete]
|
||||
func (h *RolPagesHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
if err := h.service.Delete(ctx, id); err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully deleted RolPages", nil)
|
||||
}
|
||||
|
||||
// GetTree godoc
|
||||
//
|
||||
// @Summary Get tree of Role Pages
|
||||
// @Description Retrieve a hierarchical tree of Role Pages
|
||||
// @Tags roles-pages
|
||||
// @Produce json
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages/tree [get]
|
||||
func (h *RolPagesHandler) GetTree(c *gin.Context) {
|
||||
activeParam := c.Query("active")
|
||||
var activeFilter *bool // default nil (no filter / ambil semua)
|
||||
if activeParam != "" {
|
||||
isActive := activeParam == "true" || activeParam == "1"
|
||||
activeFilter = &isActive
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
tree, err := h.service.GetTree(ctx, activeFilter)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved RolPages", tree)
|
||||
}
|
||||
|
||||
// GetTreeByLevel godoc
|
||||
//
|
||||
// @Summary Get tree of Role Pages by level
|
||||
// @Description Retrieve a hierarchical tree of Role Pages up to a specific level
|
||||
// @Tags roles-pages
|
||||
// @Produce json
|
||||
// @Param level path int true "Hierarchy Level"
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/pages/tree/level/{level} [get]
|
||||
func (h *RolPagesHandler) GetTreeByLevel(c *gin.Context) {
|
||||
level, err := strconv.ParseInt(c.Param("level"), 10, 16)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid level format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
activeParam := c.Query("active")
|
||||
var activeFilter *bool // default nil (no filter / ambil semua)
|
||||
if activeParam != "" {
|
||||
isActive := activeParam == "true" || activeParam == "1"
|
||||
activeFilter = &isActive
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
tree, err := h.service.GetTreeByLevel(ctx, int16(level), activeFilter)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved RolPages", tree)
|
||||
}
|
||||
+502
@@ -0,0 +1,502 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
roleService "service/internal/master/role/permission"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/logger"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type RolPermissionHandler struct {
|
||||
service roleService.Service
|
||||
}
|
||||
|
||||
func NewRolPermissionHandler(service roleService.Service) *RolPermissionHandler {
|
||||
return &RolPermissionHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *RolPermissionHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/roles/permissions") // get by role
|
||||
{
|
||||
group.GET("", h.GetList)
|
||||
group.GET("/search", h.Search)
|
||||
group.GET("/:id", h.GetDetail) // using role id
|
||||
group.GET("/role/:role", h.GetPermissionTreeRole) // get permission tree by role keycloak
|
||||
group.POST("", h.Create)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.DELETE("/:id", h.Delete)
|
||||
group.GET("/rolemaster/:id", h.GetRolePagesList)
|
||||
}
|
||||
}
|
||||
|
||||
// GetList godoc
|
||||
//
|
||||
// @Summary Get list of Role Permissions
|
||||
// @Description Retrieve a paginated list of Role Permissions
|
||||
// @Tags roles-permissions
|
||||
// @Produce json
|
||||
// @Param limit query int false "Number of items per page" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Param sort query string false "Sort fields (e.g. +name,-created_at)"
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions [get]
|
||||
func (h *RolPermissionHandler) GetList(c *gin.Context) {
|
||||
// Parse Limit & Offset dengan fallback ke page & page_size
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if pageSizeStr := c.Query("limit"); pageSizeStr != "" {
|
||||
limit, _ = strconv.Atoi(pageSizeStr)
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
if pageStr := c.Query("offset"); pageStr != "" {
|
||||
if page, err := strconv.Atoi(pageStr); err == nil && page > 0 && limit > 0 {
|
||||
offset = (page - 1) * limit
|
||||
}
|
||||
}
|
||||
|
||||
activeParam := c.Query("active")
|
||||
var activeFilter *bool // default nil (no filter / ambil semua)
|
||||
if activeParam != "" {
|
||||
isActive := activeParam == "true" || activeParam == "1"
|
||||
activeFilter = &isActive
|
||||
}
|
||||
|
||||
// Parse parameter sort (format: sort=column1,-column2,+column3)
|
||||
// -column untuk DESC, +column atau column untuk ASC
|
||||
var sorts []string
|
||||
if sortParam := c.Query("sort"); sortParam != "" {
|
||||
sorts = strings.Split(sortParam, ",")
|
||||
// Validasi dan bersihkan sort parameters
|
||||
for i, sort := range sorts {
|
||||
sorts[i] = strings.TrimSpace(sort)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetList(ctx, limit, offset, sorts, activeFilter)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
data := result["data"]
|
||||
total := result["total"].(int64)
|
||||
limitVal := result["limit"].(int)
|
||||
offsetVal := result["offset"].(int)
|
||||
|
||||
page := 1
|
||||
if limitVal > 0 {
|
||||
page = (offsetVal / limitVal) + 1
|
||||
}
|
||||
totalPages := 0
|
||||
if limitVal > 0 {
|
||||
totalPages = int(math.Ceil(float64(total) / float64(limitVal)))
|
||||
}
|
||||
|
||||
meta := response.Meta{Page: page, Limit: limitVal, Total: int(total), TotalPages: totalPages}
|
||||
response.Paginated(c, http.StatusOK, "Successfully retrieved RolPermission list", data, meta)
|
||||
}
|
||||
|
||||
// GetDetail godoc
|
||||
//
|
||||
// @Summary Get Role Permission detail
|
||||
// @Description Retrieve detailed information about a specific Role Permission
|
||||
// @Tags roles-permissions
|
||||
// @Produce json
|
||||
// @Param id path int true "Role Permission ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions/{id} [get]
|
||||
func (h *RolPermissionHandler) GetDetail(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetDetail(ctx, id)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved RolPermission detail", result)
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
//
|
||||
// @Summary Search Role Permissions
|
||||
// @Description Search Role Permissions records using dynamic filters
|
||||
// @Tags roles-permissions
|
||||
// @Produce json
|
||||
// @Param limit query int false "Limit per page" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Param sort query string false "Sort fields"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions/search [get]
|
||||
func (h *RolPermissionHandler) Search(c *gin.Context) {
|
||||
// Parse Limit & Offset dengan fallback ke page & page_size
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if pageSizeStr := c.Query("limit"); pageSizeStr != "" {
|
||||
limit, _ = strconv.Atoi(pageSizeStr)
|
||||
}
|
||||
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
if pageStr := c.Query("offset"); pageStr != "" {
|
||||
if page, err := strconv.Atoi(pageStr); err == nil && page > 0 && limit > 0 {
|
||||
offset = (page - 1) * limit
|
||||
}
|
||||
}
|
||||
|
||||
// Ambil parameter filter secara dinamis
|
||||
filters := make(map[string]interface{})
|
||||
|
||||
for key, values := range c.Request.URL.Query() {
|
||||
if key == "page" || key == "limit" || key == "page_size" || key == "offset" || key == "sort" {
|
||||
continue
|
||||
}
|
||||
if len(values) > 0 && values[0] != "" {
|
||||
filters[key] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parameter sort (format: sort=column1,-column2,+column3)
|
||||
// -column untuk DESC, +column atau column untuk ASC
|
||||
var sorts []string
|
||||
if sortParam := c.Query("sort"); sortParam != "" {
|
||||
sorts = strings.Split(sortParam, ",")
|
||||
// Validasi dan bersihkan sort parameters
|
||||
for i, sort := range sorts {
|
||||
sorts[i] = strings.TrimSpace(sort)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
logger.Default().WithContext(ctx).Info("Search request",
|
||||
logger.String("filters", fmt.Sprintf("%v", filters)),
|
||||
logger.String("sorts", fmt.Sprintf("%v", sorts)),
|
||||
logger.Int("offset", offset),
|
||||
logger.Int("limit", limit))
|
||||
|
||||
// Panggil service dengan parameter sort tambahan
|
||||
result, err := h.service.Search(ctx, filters, sorts, limit, offset)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
// Extract data dari map service untuk response format
|
||||
data := result["data"]
|
||||
total := result["total"].(int64)
|
||||
limitVal := result["limit"].(int)
|
||||
offsetVal := result["offset"].(int)
|
||||
|
||||
page := 1
|
||||
if limitVal > 0 {
|
||||
page = (offsetVal / limitVal) + 1
|
||||
}
|
||||
totalPages := 0
|
||||
if limitVal > 0 {
|
||||
totalPages = int(math.Ceil(float64(total) / float64(limitVal)))
|
||||
}
|
||||
|
||||
meta := response.Meta{
|
||||
Page: page,
|
||||
Limit: limitVal,
|
||||
Total: int(total),
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
|
||||
response.Paginated(c, http.StatusOK, "Successfully retrieved RolPermission search results", data, meta)
|
||||
}
|
||||
|
||||
// GetPermissionTree godoc
|
||||
//
|
||||
// @Summary Get Permission Tree
|
||||
// @Description Retrieve a permission tree
|
||||
// @Tags roles-permissions
|
||||
// @Produce json
|
||||
// @Param role path string true "Role Keycloak"
|
||||
// @Param groups query string false "Comma-separated groups"
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions/tree/{role} [get]
|
||||
func (h *RolPermissionHandler) GetPermissionTree(c *gin.Context) {
|
||||
roleKeycloak := c.Param("role")
|
||||
|
||||
// Get groups from query parameter (comma-separated)
|
||||
groupsParam := c.Query("groups")
|
||||
var groups []string
|
||||
if groupsParam != "" {
|
||||
groups = strings.Split(groupsParam, ",")
|
||||
// Trim spaces from each group
|
||||
for i, group := range groups {
|
||||
groups[i] = strings.TrimSpace(group)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse active parameter - handle "true", "false", "1", "0"
|
||||
activeParam := c.Query("active")
|
||||
var activeOnly *bool // default nil (no filter)
|
||||
if activeParam != "" {
|
||||
isActive := activeParam == "true" || activeParam == "1"
|
||||
activeOnly = &isActive
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetRolePermissionTree(ctx, roleKeycloak, groups, activeOnly)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
// Jika role disediakan tapi tidak ada data, return empty result
|
||||
if roleKeycloak != "" && (result == nil || len(result.Data.Access) == 0) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "success",
|
||||
"message": "No permissions found for the specified role",
|
||||
"data": []interface{}{},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Return the result in the desired format
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "success",
|
||||
"message": "Successfully retrieved RolPages",
|
||||
"data": result.Data,
|
||||
})
|
||||
}
|
||||
|
||||
// GetPermissionTreeRole godoc
|
||||
//
|
||||
// @Summary Get Permission Tree by Role
|
||||
// @Description Retrieve a permission tree by Keycloak role
|
||||
// @Tags roles-permissions
|
||||
// @Produce json
|
||||
// @Param role path string true "Role Keycloak"
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions/role/{role} [get]
|
||||
func (h *RolPermissionHandler) GetPermissionTreeRole(c *gin.Context) {
|
||||
roleKeycloak := c.Param("role")
|
||||
|
||||
// Parse active parameter - handle "true", "false", "1", "0"
|
||||
activeParam := c.Query("active")
|
||||
var activeOnly *bool // default nil (no filter)
|
||||
if activeParam != "" {
|
||||
isActive := activeParam == "true" || activeParam == "1"
|
||||
activeOnly = &isActive
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
result, err := h.service.GetRolePermissionTreeRole(ctx, roleKeycloak, activeOnly)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
// Jika role disediakan tapi tidak ada data, return empty result
|
||||
if roleKeycloak != "" && (result == nil || len(result.Data.Access) == 0) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "success",
|
||||
"message": "No permissions found for the specified role",
|
||||
"data": []interface{}{},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Return the result in the desired format
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "success",
|
||||
"message": "Successfully retrieved RolPages",
|
||||
"data": result.Data,
|
||||
})
|
||||
}
|
||||
|
||||
// Create godoc
|
||||
//
|
||||
// @Summary Create new Role Permission
|
||||
// @Description Create a new Role Permission record
|
||||
// @Tags roles-permissions
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body roleService.RolPermissionRequest true "Payload"
|
||||
// @Success 201 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions [post]
|
||||
func (h *RolPermissionHandler) Create(c *gin.Context) {
|
||||
var req roleService.RolPermissionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
created, err := h.service.Create(ctx, req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusCreated, "Successfully created RolPermission", created)
|
||||
}
|
||||
|
||||
// Update godoc
|
||||
//
|
||||
// @Summary Update an existing Role Permission
|
||||
// @Description Update details of an existing Role Permission record by ID
|
||||
// @Tags roles-permissions
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Role Permission ID"
|
||||
// @Param request body roleService.RolPermissionRequest true "Payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions/{id} [put]
|
||||
func (h *RolPermissionHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req roleService.RolPermissionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid request body", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
updated, err := h.service.Update(ctx, id, req)
|
||||
if err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully updated RolPermission", updated)
|
||||
}
|
||||
|
||||
// Delete godoc
|
||||
//
|
||||
// @Summary Delete a Role Permission
|
||||
// @Description Delete a Role Permission record by ID
|
||||
// @Tags roles-permissions
|
||||
// @Produce json
|
||||
// @Param id path int true "Role Permission ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Security BearerAuth
|
||||
// @Router /roles/permissions/{id} [delete]
|
||||
func (h *RolPermissionHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Invalid ID format", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
if err := h.service.Delete(ctx, id); err != nil {
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, http.StatusOK, "Successfully deleted RolPermission", nil)
|
||||
}
|
||||
|
||||
// GetRolePagesList mengambil daftar role beserta page dan permission-nya
|
||||
// @Summary Get Role Pages Access List
|
||||
// @Description Retrieve a paginated list of RolePages grouped by Role with Tree Hierarchy
|
||||
// @Tags roles-permissions
|
||||
// @Produce json
|
||||
// @Param id path string true "Role Master ID"
|
||||
// @Param page query integer false "Page number" default(1)
|
||||
// @Param limit query integer false "Items per page" default(10)
|
||||
// @Param active query boolean false "Filter by active status"
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /roles/permissions/rolemaster/{id} [get]
|
||||
func (h *RolPermissionHandler) GetRolePagesList(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
roleID := c.Param("id")
|
||||
|
||||
// 1. Ambil Parameter Pagination
|
||||
pageStr := c.DefaultQuery("page", "1")
|
||||
limitStr := c.DefaultQuery("limit", "10")
|
||||
|
||||
page, err := strconv.Atoi(pageStr)
|
||||
if err != nil || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit < -1 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
offset := (page - 1) * limit
|
||||
if limit == -1 {
|
||||
offset = 0 // Jika limit -1, fetch all data
|
||||
}
|
||||
|
||||
// 2. Ambil Parameter Filter Active
|
||||
defaultActive := true
|
||||
activeFilter := &defaultActive
|
||||
if activeStr := c.Query("active"); activeStr != "" {
|
||||
parsedActive, err := strconv.ParseBool(activeStr)
|
||||
if err == nil {
|
||||
activeFilter = &parsedActive
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Panggil Service layer
|
||||
res, err := h.service.GetRolePagesList(ctx, roleID, limit, offset, activeFilter)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"status": "error",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Kalkulasi Metadata & Response Sesuai Format
|
||||
total := res["total"].(int64)
|
||||
totalPages := 1
|
||||
if limit > 0 {
|
||||
totalPages = int((total + int64(limit) - 1) / int64(limit))
|
||||
}
|
||||
|
||||
// Format JSON Response
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "success",
|
||||
"message": "Successfully retrieved RolPages list",
|
||||
"data": res["data"],
|
||||
"meta": gin.H{
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package reference
|
||||
|
||||
import (
|
||||
stdErrors "errors"
|
||||
"net/http"
|
||||
"service/internal/interfaces/satusehat"
|
||||
"service/internal/satusehat/reference/auth"
|
||||
"service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AuthHandler menangani endpoint HTTP untuk Auth Satu Sehat.
|
||||
type AuthHandler struct {
|
||||
service auth.Service
|
||||
}
|
||||
|
||||
// RegisterRoutes mendaftarkan endpoint handler ini ke router Gin
|
||||
func (h *AuthHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/reference")
|
||||
{
|
||||
group.GET("/auth/token", h.GetToken)
|
||||
group.POST("/auth/token/refresh", h.RefreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
// NewAuthHandler membuat instance baru dari AuthHandler.
|
||||
func NewAuthHandler(service auth.Service) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// handleSatuSehatError mengekstrak OperationOutcome agar JSON bisa tampil terstruktur di response
|
||||
func handleSatuSehatError(c *gin.Context, err error) {
|
||||
var ssErr *satusehat.ErrorOperationOutcome
|
||||
if stdErrors.As(err, &ssErr) {
|
||||
c.JSON(ssErr.StatusCode, gin.H{
|
||||
"status": "error",
|
||||
"message": ssErr.Outcome,
|
||||
"error": gin.H{
|
||||
"severity": "error",
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
appErr := errors.FromError(err)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
}
|
||||
|
||||
// GetToken godoc
|
||||
//
|
||||
// @Summary Get SatuSehat Access Token
|
||||
// @Description Mendapatkan token aktif untuk API Satu Sehat Kemenkes (menggunakan cache internal)
|
||||
// @Tags Satu Sehat - Auth
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /satusehat/reference/auth/token [get]
|
||||
// @Security BearerAuth
|
||||
func (h *AuthHandler) GetToken(c *gin.Context) {
|
||||
data, err := h.service.GetToken(c.Request.Context())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan token Satu Sehat", data)
|
||||
}
|
||||
|
||||
// RefreshToken godoc
|
||||
//
|
||||
// @Summary Refresh SatuSehat Access Token
|
||||
// @Description Memaksa request token baru dari API Satu Sehat Kemenkes (bypass cache)
|
||||
// @Tags Satu Sehat - Auth
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /satusehat/reference/auth/token/refresh [post]
|
||||
// @Security BearerAuth
|
||||
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
||||
data, err := h.service.RefreshToken(c.Request.Context())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil me-refresh token Satu Sehat", data)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package reference
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/reference/kfa"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type KFAHandler struct {
|
||||
service kfa.Service
|
||||
}
|
||||
|
||||
func NewKFAHandler(service kfa.Service) *KFAHandler {
|
||||
return &KFAHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// GetByCode godoc
|
||||
//
|
||||
// @Summary Cari Produk KFA berdasarkan Kode
|
||||
// @Description Mencari detail produk farmasi/alkes dari API Kamus Farmasi dan Alat Kesehatan (KFA) Satu Sehat
|
||||
// @Tags Satu Sehat - KFA
|
||||
// @Produce json
|
||||
// @Param code path string true "Kode KFA (contoh: 93000469)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/kfa/products/{code} [get]
|
||||
// @Security BearerAuth
|
||||
func (h *KFAHandler) GetByCode(c *gin.Context) {
|
||||
code := c.Param("code")
|
||||
data, err := h.service.GetByCode(c.Request.Context(), code)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data produk KFA", data)
|
||||
}
|
||||
|
||||
// GetProducts godoc
|
||||
//
|
||||
// @Summary Daftar Produk KFA
|
||||
// @Description Mengambil daftar produk KFA dengan kapabilitas paginasi
|
||||
// @Tags Satu Sehat - KFA
|
||||
// @Produce json
|
||||
// @Param page query int false "Nomor Halaman (default: 1)"
|
||||
// @Param size query int false "Jumlah Data (default: 10)"
|
||||
// @Param product_type query string false "Tipe Produk (farmasi/alkes)"
|
||||
// @Param keyword query string false "Kata Kunci Pencarian"
|
||||
// @Param from_ query string false "Parameter waktu (from_)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/kfa/products [get]
|
||||
// @Security BearerAuth
|
||||
func (h *KFAHandler) GetProducts(c *gin.Context) {
|
||||
var params kfa.KFASearchParams
|
||||
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format parameter tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.GetProducts(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mengambil daftar produk KFA", data)
|
||||
}
|
||||
|
||||
func (h *KFAHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/reference/kfa")
|
||||
{
|
||||
group.GET("/products/:code", h.GetByCode)
|
||||
group.GET("/products", h.GetProducts)
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package reference
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/reference/location"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type LocationHandler struct {
|
||||
service location.Service
|
||||
}
|
||||
|
||||
func NewLocationHandler(service location.Service) *LocationHandler {
|
||||
return &LocationHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// GetByID godoc
|
||||
//
|
||||
// @Summary Cari Lokasi (Satu Sehat) berdasarkan ID
|
||||
// @Description Mencari data Location FHIR Satu Sehat berdasarkan ID
|
||||
// @Tags Satu Sehat - Location
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/location/{id} [get]
|
||||
// @Security BearerAuth
|
||||
func (h *LocationHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
data, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data lokasi", data)
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
//
|
||||
// @Summary Pencarian Lokasi (Satu Sehat)
|
||||
// @Description Mencari data Location berdasarkan parameter
|
||||
// @Tags Satu Sehat - Location
|
||||
// @Produce json
|
||||
// @Param name query string false "Nama Lokasi"
|
||||
// @Param organization query string false "ID Organisasi Pemilik"
|
||||
// @Param identifier query string false "Identifier Lokasi"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/location [get]
|
||||
// @Security BearerAuth
|
||||
func (h *LocationHandler) Search(c *gin.Context) {
|
||||
var params location.LocationSearchParams
|
||||
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format pencarian tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Search(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data lokasi", data)
|
||||
}
|
||||
|
||||
func (h *LocationHandler) Create(c *gin.Context) {
|
||||
var payload map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format request tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Create(c.Request.Context(), payload)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Berhasil membuat data lokasi", data)
|
||||
}
|
||||
|
||||
func (h *LocationHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var payload map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format request tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Update(c.Request.Context(), id, payload)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mengubah data lokasi", data)
|
||||
}
|
||||
|
||||
func (h *LocationHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var payload interface{}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format request tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Patch(c.Request.Context(), id, payload)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil melakukan patch data lokasi", data)
|
||||
}
|
||||
|
||||
// RegisterRoutes mendaftarkan endpoint handler ini ke router Gin
|
||||
func (h *LocationHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/reference")
|
||||
{
|
||||
group.GET("/location/:id", h.GetByID)
|
||||
group.GET("/location", h.Search)
|
||||
group.POST("/location", h.Create)
|
||||
group.PUT("/location/:id", h.Update)
|
||||
group.PATCH("/location/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package reference
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/reference/organization"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OrganizationHandler struct {
|
||||
service organization.Service
|
||||
}
|
||||
|
||||
func NewOrganizationHandler(service organization.Service) *OrganizationHandler {
|
||||
return &OrganizationHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// GetByID godoc
|
||||
//
|
||||
// @Summary Cari Organisasi (Satu Sehat) berdasarkan ID
|
||||
// @Description Mencari data Organization FHIR Satu Sehat berdasarkan ID
|
||||
// @Tags Satu Sehat - Organization
|
||||
// @Produce json
|
||||
// @Param id path string true "Organization ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/organization/{id} [get]
|
||||
// @Security BearerAuth
|
||||
func (h *OrganizationHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
data, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data organisasi", data)
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
//
|
||||
// @Summary Pencarian Organisasi (Satu Sehat)
|
||||
// @Description Mencari data Organization berdasarkan Name atau PartOf
|
||||
// @Tags Satu Sehat - Organization
|
||||
// @Produce json
|
||||
// @Param name query string false "Nama Organisasi"
|
||||
// @Param partof query string false "ID Organisasi Induk"
|
||||
// @Param identifier query string false "Identifier Organisasi"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/organization [get]
|
||||
// @Security BearerAuth
|
||||
func (h *OrganizationHandler) Search(c *gin.Context) {
|
||||
var params organization.OrganizationSearchParams
|
||||
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format pencarian tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Validasi mandiri sebelum menembak ke API Kemenkes
|
||||
if params.Name == "" && params.PartOf == "" && params.Identifier == "" {
|
||||
response.Error(c, http.StatusBadRequest, "Parameter pencarian tidak lengkap", "Harap masukkan minimal salah satu parameter query: name, partof, atau identifier")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Search(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data organisasi", data)
|
||||
}
|
||||
|
||||
func (h *OrganizationHandler) Create(c *gin.Context) {
|
||||
var payload map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format request tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Create(c.Request.Context(), payload)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Berhasil membuat data organisasi", data)
|
||||
}
|
||||
|
||||
func (h *OrganizationHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var payload map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format request tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Update(c.Request.Context(), id, payload)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mengubah data organisasi", data)
|
||||
}
|
||||
|
||||
func (h *OrganizationHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
// JSON Patch biasanya array of objects, jadi gunakan interface{} general
|
||||
var payload interface{}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format request tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Patch(c.Request.Context(), id, payload)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil melakukan patch data organisasi", data)
|
||||
}
|
||||
|
||||
// RegisterRoutes mendaftarkan endpoint handler ini ke router Gin
|
||||
func (h *OrganizationHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/reference")
|
||||
{
|
||||
group.GET("/organization/:id", h.GetByID)
|
||||
group.GET("/organization", h.Search)
|
||||
group.POST("/organization", h.Create)
|
||||
group.PUT("/organization/:id", h.Update)
|
||||
group.PATCH("/organization/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package reference
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"service/internal/satusehat/reference/patient"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// PatientHandler menangani endpoint HTTP untuk resource Patient Satu Sehat.
|
||||
type PatientHandler struct {
|
||||
service patient.Service
|
||||
}
|
||||
|
||||
// RegisterRoutes mendaftarkan endpoint handler ini ke router Gin
|
||||
func (h *PatientHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/reference")
|
||||
{
|
||||
group.GET("/patient/nik/:nik", h.GetByNIK)
|
||||
group.GET("/patient/:id", h.GetByID)
|
||||
group.GET("/patient", h.Search)
|
||||
group.POST("/patient", h.Create)
|
||||
}
|
||||
}
|
||||
|
||||
// NewPatientHandler membuat instance baru dari PatientHandler.
|
||||
func NewPatientHandler(service patient.Service) *PatientHandler {
|
||||
return &PatientHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// GetByNIK godoc
|
||||
//
|
||||
// @Summary Cari Pasien (Satu Sehat) berdasarkan NIK
|
||||
// @Description Mencari data pasien FHIR Satu Sehat berdasarkan Nomor Induk Kependudukan (NIK)
|
||||
// @Tags Satu Sehat - Patient
|
||||
// @Produce json
|
||||
// @Param nik path string true "Nomor Induk Kependudukan"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/patient/nik/{nik} [get]
|
||||
// @Security BearerAuth
|
||||
func (h *PatientHandler) GetByNIK(c *gin.Context) {
|
||||
nik := c.Param("nik")
|
||||
data, err := h.service.GetByNIK(c.Request.Context(), nik)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data pasien", data)
|
||||
}
|
||||
|
||||
// GetByID godoc
|
||||
//
|
||||
// @Summary Cari Pasien (Satu Sehat) berdasarkan ID
|
||||
// @Description Mencari data pasien FHIR Satu Sehat berdasarkan IHS Number / ID
|
||||
// @Tags Satu Sehat - Patient
|
||||
// @Produce json
|
||||
// @Param id path string true "IHS Number / Patient ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/patient/{id} [get]
|
||||
// @Security BearerAuth
|
||||
func (h *PatientHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
data, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data pasien", data)
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
//
|
||||
// @Summary Pencarian Pasien (Satu Sehat) Multi-Parameter
|
||||
// @Description Mencari data pasien FHIR Satu Sehat berdasarkan kombinasi parameter (Nama, NIK, NIK Ibu, Tanggal Lahir, Gender)
|
||||
// @Tags Satu Sehat - Patient
|
||||
// @Produce json
|
||||
// @Param nik query string false "Nomor Induk Kependudukan"
|
||||
// @Param nik_ibu query string false "Nomor Induk Kependudukan Ibu (Untuk bayi)"
|
||||
// @Param name query string false "Nama Pasien"
|
||||
// @Param birthdate query string false "Tanggal Lahir (YYYY-MM-DD)"
|
||||
// @Param gender query string false "Jenis Kelamin (male/female)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/patient [get]
|
||||
// @Security BearerAuth
|
||||
func (h *PatientHandler) Search(c *gin.Context) {
|
||||
var params patient.PatientSearchParams
|
||||
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format pencarian tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Search(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data pasien", data)
|
||||
}
|
||||
|
||||
// Create godoc
|
||||
//
|
||||
// @Summary Daftar Pasien Baru (Satu Sehat)
|
||||
// @Description Mendaftarkan data pasien baru ke API Satu Sehat dan mengembalikan IHS Number
|
||||
// @Tags Satu Sehat - Patient
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body patient.CreatePatientRequest true "Data Pasien Baru"
|
||||
// @Success 201 {object} response.Response
|
||||
// @Router /satusehat/reference/patient [post]
|
||||
// @Security BearerAuth
|
||||
func (h *PatientHandler) Create(c *gin.Context) {
|
||||
var req patient.CreatePatientRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format request tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Berhasil mendaftarkan pasien", data)
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package reference
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/reference/practitioner"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PractitionerHandler struct {
|
||||
service practitioner.Service
|
||||
}
|
||||
|
||||
func NewPractitionerHandler(service practitioner.Service) *PractitionerHandler {
|
||||
return &PractitionerHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// GetByNIK godoc
|
||||
//
|
||||
// @Summary Cari Tenaga Medis (Satu Sehat) berdasarkan NIK
|
||||
// @Description Mencari data Practitioner FHIR Satu Sehat berdasarkan NIK
|
||||
// @Tags Satu Sehat - Practitioner
|
||||
// @Produce json
|
||||
// @Param nik path string true "Nomor Induk Kependudukan"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/practitioner/nik/{nik} [get]
|
||||
// @Security BearerAuth
|
||||
func (h *PractitionerHandler) GetByNIK(c *gin.Context) {
|
||||
nik := c.Param("nik")
|
||||
data, err := h.service.GetByNIK(c.Request.Context(), nik)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data tenaga medis", data)
|
||||
}
|
||||
|
||||
// GetByID godoc
|
||||
//
|
||||
// @Summary Cari Tenaga Medis (Satu Sehat) berdasarkan ID
|
||||
// @Description Mencari data Practitioner FHIR Satu Sehat berdasarkan IHS Number / ID
|
||||
// @Tags Satu Sehat - Practitioner
|
||||
// @Produce json
|
||||
// @Param id path string true "IHS Number / Practitioner ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/practitioner/{id} [get]
|
||||
// @Security BearerAuth
|
||||
func (h *PractitionerHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
data, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data tenaga medis", data)
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
//
|
||||
// @Summary Pencarian Tenaga Medis (Satu Sehat) Multi-Parameter
|
||||
// @Description Mencari data Practitioner FHIR Satu Sehat berdasarkan parameter
|
||||
// @Tags Satu Sehat - Practitioner
|
||||
// @Produce json
|
||||
// @Param nik query string false "Nomor Induk Kependudukan"
|
||||
// @Param name query string false "Nama Tenaga Medis"
|
||||
// @Param gender query string false "Jenis Kelamin (male/female)"
|
||||
// @Param birthdate query string false "Tanggal Lahir (YYYY-MM-DD)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /satusehat/reference/practitioner [get]
|
||||
// @Security BearerAuth
|
||||
func (h *PractitionerHandler) Search(c *gin.Context) {
|
||||
var params practitioner.PractitionerSearchParams
|
||||
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "Format pencarian tidak valid", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, err := h.service.Search(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Berhasil mendapatkan data tenaga medis", data)
|
||||
}
|
||||
|
||||
// RegisterRoutes mendaftarkan endpoint handler ini ke router Gin
|
||||
func (h *PractitionerHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/reference")
|
||||
{
|
||||
group.GET("/practitioner/nik/:nik", h.GetByNIK)
|
||||
group.GET("/practitioner/:id", h.GetByID)
|
||||
group.GET("/practitioner", h.Search)
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"service/internal/satusehat/usecase/allergyintolerance"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AllergyIntoleranceHandler struct{ service allergyintolerance.Service }
|
||||
|
||||
func NewAllergyIntoleranceHandler(s allergyintolerance.Service) *AllergyIntoleranceHandler {
|
||||
return &AllergyIntoleranceHandler{service: s}
|
||||
}
|
||||
|
||||
func (h *AllergyIntoleranceHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
g := router.Group("/satusehat/allergyintolerance")
|
||||
g.POST("", h.Create)
|
||||
g.GET("", h.Search)
|
||||
g.GET("/:id", h.GetByID)
|
||||
g.PUT("/:id", h.Update)
|
||||
g.PATCH("/:id", h.Patch)
|
||||
}
|
||||
|
||||
func (h *AllergyIntoleranceHandler) Create(c *gin.Context) {
|
||||
var req allergyintolerance.AllergyIntoleranceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *AllergyIntoleranceHandler) Update(c *gin.Context) {
|
||||
var req allergyintolerance.AllergyIntoleranceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Update(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *AllergyIntoleranceHandler) Patch(c *gin.Context) {
|
||||
var req allergyintolerance.AllergyIntolerancePatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid patch", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Patch(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *AllergyIntoleranceHandler) GetByID(c *gin.Context) {
|
||||
res, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *AllergyIntoleranceHandler) Search(c *gin.Context) {
|
||||
res, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"service/internal/satusehat/usecase/careplan"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CarePlanHandler struct{ service careplan.Service }
|
||||
|
||||
func NewCarePlanHandler(s careplan.Service) *CarePlanHandler { return &CarePlanHandler{service: s} }
|
||||
|
||||
func (h *CarePlanHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
g := router.Group("/satusehat/careplan")
|
||||
g.POST("", h.Create)
|
||||
g.GET("", h.Search)
|
||||
g.GET("/:id", h.GetByID)
|
||||
g.PUT("/:id", h.Update)
|
||||
g.PATCH("/:id", h.Patch)
|
||||
}
|
||||
|
||||
func (h *CarePlanHandler) Create(c *gin.Context) {
|
||||
var req careplan.CarePlanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *CarePlanHandler) Update(c *gin.Context) {
|
||||
var req careplan.CarePlanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Update(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *CarePlanHandler) Patch(c *gin.Context) {
|
||||
var req careplan.CarePlanPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid patch", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Patch(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *CarePlanHandler) GetByID(c *gin.Context) {
|
||||
res, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *CarePlanHandler) Search(c *gin.Context) {
|
||||
res, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
clinicalimpression "service/internal/satusehat/usecase/clinicalImpression"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ClinicalImpressionHandler struct{ service clinicalimpression.Service }
|
||||
|
||||
func NewClinicalImpressionHandler(s clinicalimpression.Service) *ClinicalImpressionHandler {
|
||||
return &ClinicalImpressionHandler{service: s}
|
||||
}
|
||||
|
||||
func (h *ClinicalImpressionHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
g := router.Group("/satusehat/clinicalimpression")
|
||||
g.POST("", h.Create)
|
||||
g.GET("", h.Search)
|
||||
g.GET("/:id", h.GetByID)
|
||||
g.PUT("/:id", h.Update)
|
||||
g.PATCH("/:id", h.Patch)
|
||||
}
|
||||
|
||||
func (h *ClinicalImpressionHandler) Create(c *gin.Context) {
|
||||
var req clinicalimpression.ClinicalImpressionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ClinicalImpressionHandler) Update(c *gin.Context) {
|
||||
var req clinicalimpression.ClinicalImpressionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Update(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ClinicalImpressionHandler) Patch(c *gin.Context) {
|
||||
var req clinicalimpression.ClinicalImpressionPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid patch", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Patch(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ClinicalImpressionHandler) GetByID(c *gin.Context) {
|
||||
res, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ClinicalImpressionHandler) Search(c *gin.Context) {
|
||||
res, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/composition"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CompositionHandler struct {
|
||||
service composition.Service
|
||||
}
|
||||
|
||||
func NewCompositionHandler(service composition.Service) *CompositionHandler {
|
||||
return &CompositionHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CompositionHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/composition")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CompositionHandler) Create(c *gin.Context) {
|
||||
var req composition.CompositionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created Composition", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *CompositionHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req composition.CompositionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated Composition", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *CompositionHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req composition.CompositionPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched Composition", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *CompositionHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Composition", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *CompositionHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Compositions", result.FullResponse)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/condition"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ConditionHandler struct {
|
||||
service condition.Service
|
||||
}
|
||||
|
||||
func NewConditionHandler(service condition.Service) *ConditionHandler {
|
||||
return &ConditionHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ConditionHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/condition")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ConditionHandler) Create(c *gin.Context) {
|
||||
var req condition.ConditionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created Condition", result)
|
||||
}
|
||||
|
||||
func (h *ConditionHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req condition.ConditionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated Condition", result)
|
||||
}
|
||||
|
||||
func (h *ConditionHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req condition.ConditionPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched Condition", result)
|
||||
}
|
||||
|
||||
func (h *ConditionHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Condition", result)
|
||||
}
|
||||
|
||||
func (h *ConditionHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Conditions", result)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"service/internal/satusehat/usecase/diagnosticreport"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DiagnosticReportHandler struct{ service diagnosticreport.Service }
|
||||
|
||||
func NewDiagnosticReportHandler(s diagnosticreport.Service) *DiagnosticReportHandler {
|
||||
return &DiagnosticReportHandler{service: s}
|
||||
}
|
||||
|
||||
func (h *DiagnosticReportHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
g := router.Group("/satusehat/diagnosticreport")
|
||||
g.POST("", h.Create)
|
||||
g.GET("", h.Search)
|
||||
g.GET("/:id", h.GetByID)
|
||||
g.PUT("/:id", h.Update)
|
||||
g.PATCH("/:id", h.Patch)
|
||||
}
|
||||
|
||||
func (h *DiagnosticReportHandler) Create(c *gin.Context) {
|
||||
var req diagnosticreport.DiagnosticReportRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *DiagnosticReportHandler) Update(c *gin.Context) {
|
||||
var req diagnosticreport.DiagnosticReportRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Update(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *DiagnosticReportHandler) Patch(c *gin.Context) {
|
||||
var req diagnosticreport.DiagnosticReportPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid patch", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Patch(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *DiagnosticReportHandler) GetByID(c *gin.Context) {
|
||||
res, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *DiagnosticReportHandler) Search(c *gin.Context) {
|
||||
res, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"service/internal/interfaces/satusehat"
|
||||
"service/internal/satusehat/usecase/encounter"
|
||||
pkgErrors "service/pkg/errors"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type EncounterHandler struct {
|
||||
service encounter.Service
|
||||
}
|
||||
|
||||
func NewEncounterHandler(service encounter.Service) *EncounterHandler {
|
||||
return &EncounterHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *EncounterHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/encounter")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.POST("/sync/:idxdaftar", h.SyncFromSIMRS)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
// handleSatuSehatError mengekstrak OperationOutcome agar JSON bisa tampil terstruktur
|
||||
func handleSatuSehatError(c *gin.Context, err error) {
|
||||
var ssErr *satusehat.ErrorOperationOutcome
|
||||
if errors.As(err, &ssErr) {
|
||||
c.Error(err) // Log error asli
|
||||
c.JSON(ssErr.StatusCode, gin.H{
|
||||
"status": "error",
|
||||
"message": ssErr.Outcome,
|
||||
"error": gin.H{
|
||||
"severity": "error",
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
appErr := pkgErrors.FromError(err)
|
||||
response.ErrorWithLog(c, err, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
}
|
||||
|
||||
func (h *EncounterHandler) Create(c *gin.Context) {
|
||||
var req encounter.EncounterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Di sini, Anda bisa menyimpan result.RawResponse atau result.ID ke database log jika diperlukan.
|
||||
// Contoh:
|
||||
// go logService.Create(c.Request.Context(), "encounter_create", req, result.RawResponse, http.StatusCreated)
|
||||
|
||||
response.Success(c, http.StatusCreated, "Successfully created Encounter", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *EncounterHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req encounter.EncounterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated Encounter", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *EncounterHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req encounter.EncounterPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched Encounter", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *EncounterHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Encounter", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *EncounterHandler) Search(c *gin.Context) {
|
||||
// Get query parameters string natively and fetch it directly (like: ?patient=xxx&status=active)
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Encounters", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *EncounterHandler) SyncFromSIMRS(c *gin.Context) {
|
||||
idxdaftarStr := c.Param("idxdaftar")
|
||||
idxdaftar, err := strconv.ParseInt(idxdaftarStr, 10, 64)
|
||||
if err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format idxdaftar tidak valid", nil)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.SyncFromSIMRS(c.Request.Context(), idxdaftar)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully synced Encounter from SIMRS", result.FullResponse)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/episodeofcare"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type EpisodeOfCareHandler struct {
|
||||
service episodeofcare.Service
|
||||
}
|
||||
|
||||
func NewEpisodeOfCareHandler(service episodeofcare.Service) *EpisodeOfCareHandler {
|
||||
return &EpisodeOfCareHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *EpisodeOfCareHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/episodeofcare")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *EpisodeOfCareHandler) Create(c *gin.Context) {
|
||||
var req episodeofcare.EpisodeOfCareRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created EpisodeOfCare", result)
|
||||
}
|
||||
|
||||
func (h *EpisodeOfCareHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req episodeofcare.EpisodeOfCareRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated EpisodeOfCare", result)
|
||||
}
|
||||
|
||||
func (h *EpisodeOfCareHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req episodeofcare.EpisodeOfCarePatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched EpisodeOfCare", result)
|
||||
}
|
||||
|
||||
func (h *EpisodeOfCareHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved EpisodeOfCare", result)
|
||||
}
|
||||
|
||||
func (h *EpisodeOfCareHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved EpisodeOfCare records", result)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/imagingstudy"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ImagingStudyHandler struct {
|
||||
service imagingstudy.Service
|
||||
}
|
||||
|
||||
func NewImagingStudyHandler(service imagingstudy.Service) *ImagingStudyHandler {
|
||||
return &ImagingStudyHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ImagingStudyHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/imagingstudy")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ImagingStudyHandler) Create(c *gin.Context) {
|
||||
var req imagingstudy.ImagingStudyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created ImagingStudy", result)
|
||||
}
|
||||
|
||||
func (h *ImagingStudyHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req imagingstudy.ImagingStudyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated ImagingStudy", result)
|
||||
}
|
||||
|
||||
func (h *ImagingStudyHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req imagingstudy.ImagingStudyPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched ImagingStudy", result)
|
||||
}
|
||||
|
||||
func (h *ImagingStudyHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved ImagingStudy", result)
|
||||
}
|
||||
|
||||
func (h *ImagingStudyHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved ImagingStudies", result)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"service/internal/satusehat/usecase/immunization"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ImmunizationHandler struct{ service immunization.Service }
|
||||
|
||||
func NewImmunizationHandler(s immunization.Service) *ImmunizationHandler {
|
||||
return &ImmunizationHandler{service: s}
|
||||
}
|
||||
|
||||
func (h *ImmunizationHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
g := router.Group("/satusehat/immunization")
|
||||
g.POST("", h.Create)
|
||||
g.GET("", h.Search)
|
||||
g.GET("/:id", h.GetByID)
|
||||
g.PUT("/:id", h.Update)
|
||||
g.PATCH("/:id", h.Patch)
|
||||
}
|
||||
|
||||
func (h *ImmunizationHandler) Create(c *gin.Context) {
|
||||
var req immunization.ImmunizationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ImmunizationHandler) Update(c *gin.Context) {
|
||||
var req immunization.ImmunizationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Update(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ImmunizationHandler) Patch(c *gin.Context) {
|
||||
var req immunization.ImmunizationPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid patch", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Patch(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ImmunizationHandler) GetByID(c *gin.Context) {
|
||||
res, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *ImmunizationHandler) Search(c *gin.Context) {
|
||||
res, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/medication"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MedicationHandler struct {
|
||||
service medication.Service
|
||||
}
|
||||
|
||||
func NewMedicationHandler(service medication.Service) *MedicationHandler {
|
||||
return &MedicationHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MedicationHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/medication")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MedicationHandler) Create(c *gin.Context) {
|
||||
var req medication.MedicationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created Medication", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medication.MedicationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated Medication", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medication.MedicationPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched Medication", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Medication", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Medications", result.FullResponse)
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/medicationdispense"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MedicationDispenseHandler struct {
|
||||
service medicationdispense.Service
|
||||
}
|
||||
|
||||
func NewMedicationDispenseHandler(service medicationdispense.Service) *MedicationDispenseHandler {
|
||||
return &MedicationDispenseHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *MedicationDispenseHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/medicationdispense")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MedicationDispenseHandler) Create(c *gin.Context) {
|
||||
var req medicationdispense.MedicationDispenseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created MedicationDispense", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationDispenseHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medicationdispense.MedicationDispenseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated MedicationDispense", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationDispenseHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medicationdispense.MedicationDispensePatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched MedicationDispense", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationDispenseHandler) GetByID(c *gin.Context) {
|
||||
result, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
func (h *MedicationDispenseHandler) Search(c *gin.Context) {
|
||||
result, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/medicationrequest"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MedicationRequestHandler struct {
|
||||
service medicationrequest.Service
|
||||
}
|
||||
|
||||
func NewMedicationRequestHandler(service medicationrequest.Service) *MedicationRequestHandler {
|
||||
return &MedicationRequestHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *MedicationRequestHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/medicationrequest")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MedicationRequestHandler) Create(c *gin.Context) {
|
||||
var req medicationrequest.MedicationRequestRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created MedicationRequest", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationRequestHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medicationrequest.MedicationRequestRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated MedicationRequest", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationRequestHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medicationrequest.MedicationRequestPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched MedicationRequest", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationRequestHandler) GetByID(c *gin.Context) {
|
||||
result, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
func (h *MedicationRequestHandler) Search(c *gin.Context) {
|
||||
result, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/medicationstatement"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MedicationStatementHandler struct {
|
||||
service medicationstatement.Service
|
||||
}
|
||||
|
||||
func NewMedicationStatementHandler(service medicationstatement.Service) *MedicationStatementHandler {
|
||||
return &MedicationStatementHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *MedicationStatementHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/medicationstatement")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MedicationStatementHandler) Create(c *gin.Context) {
|
||||
var req medicationstatement.MedicationStatementRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created MedicationStatement", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationStatementHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medicationstatement.MedicationStatementRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated MedicationStatement", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationStatementHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req medicationstatement.MedicationStatementPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched MedicationStatement", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *MedicationStatementHandler) GetByID(c *gin.Context) {
|
||||
result, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
func (h *MedicationStatementHandler) Search(c *gin.Context) {
|
||||
result, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/observation"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ObservationHandler struct {
|
||||
service observation.Service
|
||||
}
|
||||
|
||||
func NewObservationHandler(service observation.Service) *ObservationHandler {
|
||||
return &ObservationHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ObservationHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/observation")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ObservationHandler) Create(c *gin.Context) {
|
||||
var req observation.ObservationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created Observation", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ObservationHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req observation.ObservationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated Observation", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ObservationHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req observation.ObservationPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched Observation", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ObservationHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Observation", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ObservationHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Observations", result.FullResponse)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/procedure"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ProcedureHandler struct {
|
||||
service procedure.Service
|
||||
}
|
||||
|
||||
func NewProcedureHandler(service procedure.Service) *ProcedureHandler {
|
||||
return &ProcedureHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ProcedureHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/procedure")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ProcedureHandler) Create(c *gin.Context) {
|
||||
var req procedure.ProcedureRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created Procedure", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ProcedureHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req procedure.ProcedureRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated Procedure", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ProcedureHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req procedure.ProcedurePatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched Procedure", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ProcedureHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Procedure", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ProcedureHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved Procedures", result.FullResponse)
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/questionnaireresponse"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type QuestionnaireResponseHandler struct {
|
||||
service questionnaireresponse.Service
|
||||
}
|
||||
|
||||
func NewQuestionnaireResponseHandler(service questionnaireresponse.Service) *QuestionnaireResponseHandler {
|
||||
return &QuestionnaireResponseHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *QuestionnaireResponseHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/questionnaireresponse")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *QuestionnaireResponseHandler) Create(c *gin.Context) {
|
||||
var req questionnaireresponse.QuestionnaireResponseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created QuestionnaireResponse", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *QuestionnaireResponseHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req questionnaireresponse.QuestionnaireResponseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated QuestionnaireResponse", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *QuestionnaireResponseHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req questionnaireresponse.QuestionnaireResponsePatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched QuestionnaireResponse", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *QuestionnaireResponseHandler) GetByID(c *gin.Context) {
|
||||
result, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
func (h *QuestionnaireResponseHandler) Search(c *gin.Context) {
|
||||
result, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved", result.FullResponse)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"service/internal/satusehat/usecase/servicerequest"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ServiceRequestHandler struct {
|
||||
service servicerequest.Service
|
||||
}
|
||||
|
||||
func NewServiceRequestHandler(service servicerequest.Service) *ServiceRequestHandler {
|
||||
return &ServiceRequestHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ServiceRequestHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
group := router.Group("/satusehat/servicerequest")
|
||||
{
|
||||
group.POST("", h.Create)
|
||||
group.GET("", h.Search)
|
||||
group.GET("/:id", h.GetByID)
|
||||
group.PUT("/:id", h.Update)
|
||||
group.PATCH("/:id", h.Patch)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ServiceRequestHandler) Create(c *gin.Context) {
|
||||
var req servicerequest.ServiceRequestRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Successfully created ServiceRequest", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ServiceRequestHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req servicerequest.ServiceRequestRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully updated ServiceRequest", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ServiceRequestHandler) Patch(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req servicerequest.ServiceRequestPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
customErr := validator.TranslateError(err)
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Format permintaan patch tidak valid", customErr)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Patch(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully patched ServiceRequest", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ServiceRequestHandler) GetByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
result, err := h.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved ServiceRequest", result.FullResponse)
|
||||
}
|
||||
|
||||
func (h *ServiceRequestHandler) Search(c *gin.Context) {
|
||||
queryParams := c.Request.URL.Query()
|
||||
result, err := h.service.Search(c.Request.Context(), queryParams)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Successfully retrieved ServiceRequests", result.FullResponse)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"service/internal/satusehat/usecase/specimen"
|
||||
"service/pkg/response"
|
||||
"service/pkg/utils/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SpecimenHandler struct{ service specimen.Service }
|
||||
|
||||
func NewSpecimenHandler(s specimen.Service) *SpecimenHandler { return &SpecimenHandler{service: s} }
|
||||
|
||||
func (h *SpecimenHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
g := router.Group("/satusehat/specimen")
|
||||
g.POST("", h.Create)
|
||||
g.GET("", h.Search)
|
||||
g.GET("/:id", h.GetByID)
|
||||
g.PUT("/:id", h.Update)
|
||||
g.PATCH("/:id", h.Patch)
|
||||
}
|
||||
|
||||
func (h *SpecimenHandler) Create(c *gin.Context) {
|
||||
var req specimen.SpecimenRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusCreated, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *SpecimenHandler) Update(c *gin.Context) {
|
||||
var req specimen.SpecimenRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid request", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Update(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *SpecimenHandler) Patch(c *gin.Context) {
|
||||
var req specimen.SpecimenPatchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWithLog(c, err, http.StatusBadRequest, "Invalid patch", validator.TranslateError(err))
|
||||
return
|
||||
}
|
||||
res, err := h.service.Patch(c.Request.Context(), c.Param("id"), req)
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *SpecimenHandler) GetByID(c *gin.Context) {
|
||||
res, err := h.service.GetByID(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
func (h *SpecimenHandler) Search(c *gin.Context) {
|
||||
res, err := h.service.Search(c.Request.Context(), c.Request.URL.Query())
|
||||
if err != nil {
|
||||
handleSatuSehatError(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, http.StatusOK, "Success", res.FullResponse)
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"service/pkg/logger" // Tambahkan import ini
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
// Development mode - izinkan semua origin (hanya untuk dev!)
|
||||
if os.Getenv("APP_ENV") == "development" && os.Getenv("CORS_ALLOW_ALL") == "true" {
|
||||
log.Println("WARNING: CORS allowing all origins (development mode)")
|
||||
|
||||
// Gunakan config khusus untuk allow all origins
|
||||
config := cors.DefaultConfig()
|
||||
config.AllowAllOrigins = true
|
||||
config.AllowCredentials = false // Tidak bisa digunakan dengan AllowAllOrigins
|
||||
config.AllowMethods = []string{
|
||||
"GET", "POST", "PUT", "PATCH", "DELETE",
|
||||
"HEAD", "OPTIONS",
|
||||
}
|
||||
config.AllowHeaders = []string{
|
||||
"Origin",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Authorization",
|
||||
"X-Requested-With",
|
||||
"X-API-Key",
|
||||
"X-CSRF-Token",
|
||||
"X-Custom-Header",
|
||||
"Accept",
|
||||
"Accept-Language",
|
||||
"Accept-Encoding",
|
||||
"Access-Control-Request-Headers",
|
||||
"Access-Control-Request-Method",
|
||||
// Headers tambahan untuk Nuxt 3
|
||||
"x-use-fetch",
|
||||
"x-nuxt-base-url",
|
||||
"x-forwarded-for",
|
||||
"x-forwarded-proto",
|
||||
"x-forwarded-host",
|
||||
}
|
||||
config.MaxAge = 12 * time.Hour
|
||||
|
||||
return cors.New(config)
|
||||
}
|
||||
|
||||
// Config untuk specific origins
|
||||
config := cors.DefaultConfig()
|
||||
|
||||
// Baca allowed origins dari environment variable
|
||||
// Format: CORS_ORIGINS=http://localhost:3000,http://localhost:3001,https://myapp.com
|
||||
originsEnv := os.Getenv("CORS_ORIGINS")
|
||||
if originsEnv != "" {
|
||||
// Split by comma dan trim spaces
|
||||
origins := strings.Split(originsEnv, ",")
|
||||
for i, origin := range origins {
|
||||
origins[i] = strings.TrimSpace(origin)
|
||||
}
|
||||
config.AllowOrigins = origins
|
||||
log.Printf("CORS: Using origins from environment: %v", config.AllowOrigins)
|
||||
} else {
|
||||
// Default origins untuk Nuxt 3 development
|
||||
config.AllowOrigins = []string{
|
||||
"http://localhost:3000", // Nuxt 3 default
|
||||
"http://localhost:3001", // Nuxt 3 alternatif
|
||||
"http://localhost:3002", // Nuxt 3 alternatif
|
||||
"http://localhost:3005", // Nuxt 3 port Anda
|
||||
"http://localhost:8080", // Common dev port
|
||||
"http://localhost:5173", // Vite default port
|
||||
"http://localhost:5174", // Vite alternatif
|
||||
"https://localhost:3000", // HTTPS Nuxt
|
||||
"https://localhost:8080", // HTTPS common
|
||||
"http://meninjar.dev.rssa.id:8094", // Domain production Anda
|
||||
}
|
||||
log.Printf("CORS: Using default origins: %v", config.AllowOrigins)
|
||||
}
|
||||
|
||||
// Method yang diizinkan untuk Nuxt 3 + TypeScript
|
||||
config.AllowMethods = []string{
|
||||
"GET", "POST", "PUT", "PATCH", "DELETE",
|
||||
"HEAD", "OPTIONS",
|
||||
}
|
||||
|
||||
// Headers yang diizinkan untuk Nuxt 3 + Axios
|
||||
config.AllowHeaders = []string{
|
||||
"Origin",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Authorization",
|
||||
"X-Requested-With",
|
||||
"X-API-Key",
|
||||
"X-CSRF-Token",
|
||||
"X-Custom-Header",
|
||||
"Accept",
|
||||
"Accept-Language",
|
||||
"Accept-Encoding",
|
||||
"Access-Control-Request-Headers",
|
||||
"Access-Control-Request-Method",
|
||||
// Headers tambahan untuk Nuxt 3
|
||||
"x-use-fetch",
|
||||
"x-nuxt-base-url",
|
||||
"x-forwarded-for",
|
||||
"x-forwarded-proto",
|
||||
"x-forwarded-host",
|
||||
}
|
||||
|
||||
// Izinkan credentials (penting untuk Nuxt 3)
|
||||
config.AllowCredentials = true
|
||||
|
||||
// Preflight cache duration
|
||||
config.MaxAge = 12 * time.Hour
|
||||
|
||||
return cors.New(config)
|
||||
}
|
||||
|
||||
func LoggingMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
raw := c.Request.URL.RawQuery
|
||||
|
||||
// Ambil atau Generate Request ID untuk Tracing (Correlation ID)
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = uuid.New().String()
|
||||
}
|
||||
|
||||
// Injeksi request_id ke dalam context request
|
||||
ctx := context.WithValue(c.Request.Context(), "request_id", requestID)
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Header("X-Request-ID", requestID)
|
||||
|
||||
// Process request
|
||||
c.Next()
|
||||
|
||||
// Log using custom logger
|
||||
latency := time.Since(start)
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
statusCode := c.Writer.Status()
|
||||
|
||||
fields := []logger.Field{
|
||||
logger.String("ip", clientIP),
|
||||
logger.String("method", method),
|
||||
logger.String("path", path),
|
||||
logger.Int("status", statusCode),
|
||||
logger.Duration("latency", latency),
|
||||
logger.String("user_agent", c.Request.UserAgent()),
|
||||
}
|
||||
|
||||
if raw != "" {
|
||||
fields = append(fields, logger.String("query", raw))
|
||||
}
|
||||
|
||||
if len(c.Errors) > 0 {
|
||||
fields = append(fields, logger.String("error", c.Errors.String()))
|
||||
}
|
||||
|
||||
logCtx := logger.Default().WithContext(ctx)
|
||||
// Use appropriate log level based on status code
|
||||
if statusCode >= 500 {
|
||||
logCtx.Error("HTTP Request", fields...)
|
||||
} else if statusCode >= 400 {
|
||||
logCtx.Warn("HTTP Request", fields...)
|
||||
} else {
|
||||
logCtx.Info("HTTP Request", fields...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorMiddleware() gin.HandlerFunc {
|
||||
return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
|
||||
logger.Default().WithContext(c.Request.Context()).Error("Panic recovered", logger.Any("panic", recovered))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Internal server error",
|
||||
"code": "INTERNAL_ERROR",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func SecurityMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 1. HSTS (Strict-Transport-Security)
|
||||
// Memaksa browser hanya menggunakan HTTPS selama 1 tahun, termasuk subdomain.
|
||||
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
|
||||
|
||||
// 2. Content Security Policy (CSP) - Code Injection Protection
|
||||
// Kita longgarkan khusus untuk path /swagger agar UI bisa melakukan load JavaScript & CSS inline bawaannya.
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/swagger") {
|
||||
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none'")
|
||||
} else {
|
||||
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content")
|
||||
}
|
||||
|
||||
// 3. X-Content-Type-Options
|
||||
// Mencegah browser menebak (sniffing) MIME type.
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
|
||||
// 4. X-Frame-Options (Clickjacking Protection)
|
||||
// Mencegah website di-embed dalam iframe orang lain.
|
||||
c.Header("X-Frame-Options", "DENY")
|
||||
|
||||
// 5. X-XSS-Protection
|
||||
// Layer pertahanan lama untuk browser lama (Legacy), tapi tetap bagus untuk ada.
|
||||
c.Header("X-XSS-Protection", "1; mode=block")
|
||||
|
||||
// 6. Referrer-Policy
|
||||
// Menjaga privasi user saat klik link keluar dari aplikasi Anda.
|
||||
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
|
||||
// 7. Permissions-Policy (Feature Policy)
|
||||
// Mematikan fitur browser yang tidak dipakai (kamera, mic, lokasi) untuk mengurangi attack vector.
|
||||
c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=()")
|
||||
|
||||
// Remove Information Leakage
|
||||
c.Header("Server", "Unknown") // Atau hapus total
|
||||
c.Header("X-Powered-By", "")
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
pkgErrors "service/pkg/errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"service/internal/infrastructure/cache"
|
||||
"service/internal/infrastructure/config"
|
||||
"service/pkg/logger"
|
||||
"service/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
// Definisi Error kustom untuk autentikasi
|
||||
var (
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
ErrMissingClaims = errors.New("missing claims")
|
||||
ErrTokenExpired = errors.New("token expired")
|
||||
ErrInvalidSignature = errors.New("invalid signature")
|
||||
ErrInvalidIssuer = errors.New("invalid issuer")
|
||||
ErrInvalidAudience = errors.New("invalid audience")
|
||||
ErrMissingAuthHeader = errors.New("missing authorization header")
|
||||
ErrInvalidAuthHeader = errors.New("invalid authorization header format")
|
||||
)
|
||||
|
||||
// JWTClaims menyimpan struktur payload token yang terekstrak
|
||||
type JWTClaims struct {
|
||||
UserID string `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// AuthProvider interface for different authentication methods
|
||||
type AuthProvider interface {
|
||||
ValidateToken(tokenString string) (*JWTClaims, error)
|
||||
Name() string
|
||||
}
|
||||
|
||||
// ProviderFactory creates authentication providers based on configuration
|
||||
type ProviderFactory struct {
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewProviderFactory(config *config.Config) *ProviderFactory {
|
||||
return &ProviderFactory{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ProviderFactory) CreateProviders() []AuthProvider {
|
||||
var providers []AuthProvider
|
||||
|
||||
reqLogger := logger.Default()
|
||||
reqLogger.Info("Creating authentication providers",
|
||||
logger.String("auth_type", f.config.Auth.Type),
|
||||
logger.Bool("keycloak_enabled", f.config.Keycloak.Enabled),
|
||||
logger.String("keycloak_issuer", f.config.Keycloak.Issuer),
|
||||
logger.Int("static_tokens_len", len(f.config.Auth.StaticTokens)),
|
||||
logger.String("fallback_to", f.config.Auth.FallbackTo),
|
||||
)
|
||||
|
||||
switch f.config.Auth.Type {
|
||||
case "static":
|
||||
if len(f.config.Auth.StaticTokens) > 0 {
|
||||
providers = append(providers, NewStaticTokenProvider(f.config.Auth.StaticTokens))
|
||||
} else {
|
||||
reqLogger.Warn("No static tokens configured for static auth type", logger.String("type", "static"))
|
||||
}
|
||||
case "jwt":
|
||||
providers = append(providers, NewJWTAuthProvider())
|
||||
reqLogger.Info("JWT provider added")
|
||||
case "keycloak":
|
||||
if f.config.Keycloak.Issuer != "" {
|
||||
providers = append(providers, NewKeycloakAuthProvider(f.config))
|
||||
reqLogger.Info("Keycloak provider added")
|
||||
} else {
|
||||
reqLogger.Warn("Keycloak issuer not configured for keycloak auth type", logger.String("type", "keycloak"))
|
||||
}
|
||||
case "hybrid":
|
||||
if f.config.Keycloak.Issuer != "" {
|
||||
providers = append(providers, NewKeycloakAuthProvider(f.config))
|
||||
reqLogger.Info("Keycloak provider added for hybrid")
|
||||
} else {
|
||||
reqLogger.Warn("Keycloak issuer not configured for hybrid auth type", logger.String("type", "keycloak"))
|
||||
}
|
||||
switch f.config.Auth.FallbackTo {
|
||||
case "static":
|
||||
if len(f.config.Auth.StaticTokens) > 0 {
|
||||
providers = append(providers, NewStaticTokenProvider(f.config.Auth.StaticTokens))
|
||||
} else {
|
||||
reqLogger.Warn("No static tokens configured for hybrid fallback", logger.String("type", "static"))
|
||||
}
|
||||
case "jwt":
|
||||
providers = append(providers, NewJWTAuthProvider())
|
||||
default:
|
||||
providers = append(providers, NewJWTAuthProvider())
|
||||
reqLogger.Info("JWT fallback provider added as default")
|
||||
}
|
||||
default:
|
||||
providers = append(providers, NewJWTAuthProvider())
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
// StaticTokenProvider handles static token authentication
|
||||
type StaticTokenProvider struct {
|
||||
tokens map[string]bool
|
||||
}
|
||||
|
||||
func NewStaticTokenProvider(tokens []string) *StaticTokenProvider {
|
||||
tokenMap := make(map[string]bool)
|
||||
for _, token := range tokens {
|
||||
if token != "" {
|
||||
tokenMap[token] = true
|
||||
}
|
||||
}
|
||||
return &StaticTokenProvider{tokens: tokenMap}
|
||||
}
|
||||
|
||||
func (s *StaticTokenProvider) ValidateToken(tokenString string) (*JWTClaims, error) {
|
||||
if !s.tokens[tokenString] {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
return &JWTClaims{
|
||||
UserID: "static-user",
|
||||
Username: "static-user",
|
||||
Email: "[email protected]",
|
||||
Role: "user",
|
||||
Name: "Static User",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *StaticTokenProvider) Name() string {
|
||||
return "static"
|
||||
}
|
||||
|
||||
// JWTAuthProvider handles JWT authentication
|
||||
type JWTAuthProvider struct {
|
||||
secret string
|
||||
}
|
||||
|
||||
func NewJWTAuthProvider() *JWTAuthProvider {
|
||||
secret := os.Getenv("JWT_SECRET")
|
||||
if secret == "" {
|
||||
secret = "fallback_secret_key_change_in_production"
|
||||
}
|
||||
return &JWTAuthProvider{secret: secret}
|
||||
}
|
||||
|
||||
func (j *JWTAuthProvider) ValidateToken(tokenString string) (*JWTClaims, error) {
|
||||
parsedToken, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||
}
|
||||
return []byte(j.secret), nil
|
||||
})
|
||||
|
||||
if err != nil || !parsedToken.Valid {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
claims, ok := parsedToken.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, ErrMissingClaims
|
||||
}
|
||||
|
||||
return &JWTClaims{
|
||||
UserID: fmt.Sprintf("%v", claims["user_id"]),
|
||||
Email: fmt.Sprintf("%v", claims["email"]),
|
||||
Role: fmt.Sprintf("%v", claims["role_id"]),
|
||||
Name: fmt.Sprintf("%v", claims["name"]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (j *JWTAuthProvider) Name() string {
|
||||
return "jwt"
|
||||
}
|
||||
|
||||
// KeycloakAuthProvider handles Keycloak JWT authentication
|
||||
type KeycloakAuthProvider struct {
|
||||
jwksCache *JwksCache
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewKeycloakAuthProvider(cfg *config.Config) *KeycloakAuthProvider {
|
||||
return &KeycloakAuthProvider{
|
||||
jwksCache: NewJwksCache(cfg),
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KeycloakAuthProvider) ValidateToken(tokenString string) (*JWTClaims, error) {
|
||||
parsedToken, _, err := jwt.NewParser().ParseUnverified(tokenString, jwt.MapClaims{})
|
||||
if err != nil {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
// Extract claims for logging
|
||||
claims, ok := parsedToken.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, ErrMissingClaims
|
||||
}
|
||||
|
||||
// Check if token is expired
|
||||
if exp, ok := claims["exp"].(float64); ok {
|
||||
if time.Now().Unix() > int64(exp) {
|
||||
return nil, ErrTokenExpired
|
||||
}
|
||||
}
|
||||
|
||||
// Pastikan token yang diterima adalah Access Token ("Bearer"), bukan ID Token
|
||||
if typ, ok := claims["typ"].(string); ok {
|
||||
if typ != "Bearer" {
|
||||
return nil, fmt.Errorf("invalid token type: expected Bearer, got %s", typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Now parse with verification
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
// Verify signing method
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
// Dihilangkan logger Warn agar tidak menyebabkan log spam ketika mekanisme fallback JWT aktif
|
||||
return nil, ErrInvalidSignature
|
||||
}
|
||||
|
||||
kid, ok := token.Header["kid"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("kid header not found")
|
||||
}
|
||||
|
||||
key, err := k.jwksCache.GetKey(kid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}, jwt.WithIssuer(k.config.Keycloak.Issuer))
|
||||
|
||||
if err != nil {
|
||||
// Return specific error based on the error type
|
||||
if strings.Contains(err.Error(), "expired") {
|
||||
return nil, ErrTokenExpired
|
||||
} else if strings.Contains(err.Error(), "signature") {
|
||||
return nil, ErrInvalidSignature
|
||||
} else if strings.Contains(err.Error(), "issuer") {
|
||||
return nil, ErrInvalidIssuer
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid token: %v", err)
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
// Extract claims
|
||||
claims, ok = token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, ErrMissingClaims
|
||||
}
|
||||
|
||||
// Validasi custom untuk Audience (aud) atau Authorized Party (azp)
|
||||
// Keycloak menempatkan Client ID di 'azp' untuk Access Token
|
||||
expectedAudience := k.config.Keycloak.Audience
|
||||
if expectedAudience != "" {
|
||||
validAudience := false
|
||||
|
||||
// 1. Cek klaim azp (Authorized Party)
|
||||
if azp := getClaimString(claims, "azp"); azp == expectedAudience {
|
||||
validAudience = true
|
||||
}
|
||||
|
||||
// 2. Cek klaim aud (Audience) jika azp tidak cocok
|
||||
if !validAudience {
|
||||
if audValue, ok := claims["aud"]; ok {
|
||||
if audList, err := extractAudience(audValue); err == nil {
|
||||
for _, a := range audList {
|
||||
if a == expectedAudience {
|
||||
validAudience = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !validAudience {
|
||||
return nil, ErrInvalidAudience
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required claims
|
||||
userID := getClaimString(claims, "sub")
|
||||
if userID == "" {
|
||||
return nil, ErrMissingClaims
|
||||
}
|
||||
|
||||
// Ekstraksi nested roles dari Keycloak (realm_access.roles)
|
||||
var roleStr string
|
||||
if realmAccess, ok := claims["realm_access"].(map[string]interface{}); ok {
|
||||
if roles, ok := realmAccess["roles"].([]interface{}); ok {
|
||||
var roleList []string
|
||||
for _, r := range roles {
|
||||
roleList = append(roleList, fmt.Sprintf("%v", r))
|
||||
}
|
||||
roleStr = strings.Join(roleList, ",") // Menggabungkan array role menjadi string: "admin,user"
|
||||
}
|
||||
}
|
||||
|
||||
return &JWTClaims{
|
||||
UserID: userID,
|
||||
Username: getClaimString(claims, "preferred_username"),
|
||||
Email: getClaimString(claims, "email"),
|
||||
Role: roleStr,
|
||||
Name: getClaimString(claims, "name"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *KeycloakAuthProvider) Name() string {
|
||||
return "keycloak"
|
||||
}
|
||||
|
||||
// AuthMiddleware provides flexible authentication based on configuration and implements redis blacklist check
|
||||
func AuthMiddleware(cfg *config.Config, cacheManager *cache.Manager) gin.HandlerFunc {
|
||||
factory := NewProviderFactory(cfg)
|
||||
providers := factory.CreateProviders()
|
||||
|
||||
// Validate that we have at least one provider
|
||||
if len(providers) == 0 {
|
||||
return func(c *gin.Context) {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "authentication service not configured"})
|
||||
}
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": ErrMissingAuthHeader.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": ErrInvalidAuthHeader.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := parts[1]
|
||||
|
||||
// Cek Blacklist Token (User yang sudah logout dilarang menggunakan token yang sama)
|
||||
if cacheManager != nil {
|
||||
var isBlacklisted bool
|
||||
if err := cacheManager.Get(c.Request.Context(), "blacklist_token:"+tokenString, &isBlacklisted); err == nil && isBlacklisted {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token has been revoked or logged out"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Coba setiap provider sampai salah satu berhasil
|
||||
var claims *JWTClaims
|
||||
var err error
|
||||
var providerName string
|
||||
providerErrorDetails := make(map[string]string)
|
||||
|
||||
for _, provider := range providers {
|
||||
claims, err = provider.ValidateToken(tokenString)
|
||||
if err == nil {
|
||||
providerName = provider.Name()
|
||||
break // Berhenti jika ada yang berhasil
|
||||
}
|
||||
providerErrorDetails[provider.Name()] = err.Error()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
var finalErr error
|
||||
|
||||
if errors.Is(err, ErrTokenExpired) {
|
||||
finalErr = pkgErrors.UnauthorizedError().
|
||||
Code(pkgErrors.ErrCodeTokenExpired).
|
||||
Message(pkgErrors.GetLocalizedMessage(pkgErrors.ErrCodeTokenExpired, "id", "Token telah kadaluarsa")).
|
||||
Metadata("provider_errors", providerErrorDetails).Build()
|
||||
} else {
|
||||
finalErr = pkgErrors.UnauthorizedError().
|
||||
Code(pkgErrors.ErrCodeInvalidToken).
|
||||
Message(pkgErrors.GetLocalizedMessage(pkgErrors.ErrCodeInvalidToken, "id", "Token tidak valid")).
|
||||
Metadata("provider_errors", providerErrorDetails).Build()
|
||||
}
|
||||
|
||||
appErr := pkgErrors.FromError(finalErr)
|
||||
response.Error(c, appErr.HTTPStatus(), appErr.Error(), appErr.Metadata())
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Set informasi pengguna di konteks
|
||||
if claims != nil {
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("email", claims.Email)
|
||||
c.Set("role", claims.Role)
|
||||
c.Set("name", claims.Name)
|
||||
c.Set("role_id", claims.Role) // Kompatibilitas untuk handler lama
|
||||
c.Set("token", tokenString) // Kompatibilitas untuk handler lama
|
||||
c.Set("auth_provider", providerName)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeAuth initializes authentication configuration
|
||||
func InitializeAuth(cfg *config.Config) {
|
||||
// This function can be used to initialize global auth settings if needed
|
||||
logger.Default().Info("Authentication initialized", logger.String("auth_type", cfg.Auth.Type))
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func getClaimString(claims jwt.MapClaims, key string) string {
|
||||
if value, ok := claims[key]; ok && value != nil {
|
||||
if str, ok := value.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractAudience parses audience claim which can be a string or an array of strings
|
||||
func extractAudience(audValue interface{}) ([]string, error) {
|
||||
switch v := audValue.(type) {
|
||||
case string:
|
||||
return []string{v}, nil
|
||||
case []interface{}:
|
||||
var auds []string
|
||||
for _, a := range v {
|
||||
if s, ok := a.(string); ok {
|
||||
auds = append(auds, s)
|
||||
}
|
||||
}
|
||||
return auds, nil
|
||||
default:
|
||||
return nil, errors.New("invalid audience format")
|
||||
}
|
||||
}
|
||||
|
||||
// JwksCache and related functions
|
||||
type JwksCache struct {
|
||||
mu sync.RWMutex
|
||||
keys map[string]*rsa.PublicKey
|
||||
expiresAt time.Time
|
||||
sfGroup singleflight.Group
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewJwksCache(cfg *config.Config) *JwksCache {
|
||||
return &JwksCache{
|
||||
keys: make(map[string]*rsa.PublicKey),
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *JwksCache) GetKey(kid string) (*rsa.PublicKey, error) {
|
||||
c.mu.RLock()
|
||||
if key, ok := c.keys[kid]; ok && time.Now().Before(c.expiresAt) {
|
||||
c.mu.RUnlock()
|
||||
return key, nil
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
|
||||
// Fetch keys with singleflight to avoid concurrent fetches
|
||||
v, err, _ := c.sfGroup.Do("fetch_jwks", func() (interface{}, error) {
|
||||
return c.fetchKeys()
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := v.(map[string]*rsa.PublicKey)
|
||||
|
||||
c.mu.Lock()
|
||||
c.keys = keys
|
||||
c.expiresAt = time.Now().Add(1 * time.Hour) // cache for 1 hour
|
||||
c.mu.Unlock()
|
||||
|
||||
key, ok := keys[kid]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("key with kid %s not found", kid)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (c *JwksCache) fetchKeys() (map[string]*rsa.PublicKey, error) {
|
||||
if c.config.Keycloak.Issuer == "" {
|
||||
return nil, fmt.Errorf("keycloak issuer is not configured")
|
||||
}
|
||||
|
||||
jwksURL := c.config.Keycloak.JwksURL
|
||||
if jwksURL == "" {
|
||||
// Construct JWKS URL from issuer if not explicitly provided
|
||||
jwksURL = c.config.Keycloak.Issuer + "/protocol/openid-connect/certs"
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Get(jwksURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch JWKS: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var jwksData struct {
|
||||
Keys []struct {
|
||||
Kid string `json:"kid"`
|
||||
Kty string `json:"kty"`
|
||||
N string `json:"n"`
|
||||
E string `json:"e"`
|
||||
} `json:"keys"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&jwksData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make(map[string]*rsa.PublicKey)
|
||||
for _, key := range jwksData.Keys {
|
||||
if key.Kty != "RSA" {
|
||||
continue
|
||||
}
|
||||
pubKey, err := parseRSAPublicKey(key.N, key.E)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
keys[key.Kid] = pubKey
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// parseRSAPublicKey parses RSA public key components from base64url strings
|
||||
func parseRSAPublicKey(nStr, eStr string) (*rsa.PublicKey, error) {
|
||||
nBytes, err := base64.RawURLEncoding.DecodeString(nStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eBytes, err := base64.RawURLEncoding.DecodeString(eStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n := new(big.Int).SetBytes(nBytes)
|
||||
e := int(new(big.Int).SetBytes(eBytes).Int64())
|
||||
|
||||
return &rsa.PublicKey{
|
||||
N: n,
|
||||
E: e,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// internal/infrastructure/transport/http/middleware/rate_limit.go
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"service/internal/infrastructure/cache"
|
||||
"service/pkg/logger" // Pastikan import ini benar
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// RateLimitMiddleware creates rate limiting middleware using Redis cache
|
||||
func RateLimitMiddleware(cacheManager *cache.Manager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Increment rate limit counter
|
||||
count, err := cacheManager.IncrementRateLimit(ctx, clientIP)
|
||||
if err != nil {
|
||||
// PERBAIKAN: Gunakan logger baru dengan konteks dan field terstruktur
|
||||
logger.Default().WithContext(ctx).
|
||||
Error("Failed to increment rate limit for IP",
|
||||
logger.ErrorField(err),
|
||||
logger.String("client_ip", clientIP),
|
||||
)
|
||||
// Allow request to proceed if cache is unavailable
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Set TTL on first request
|
||||
if count == 1 {
|
||||
if err := cacheManager.SetRateLimit(ctx, clientIP, count); err != nil {
|
||||
// PERBAIKAN: Gunakan logger baru
|
||||
logger.Default().WithContext(ctx).
|
||||
Error("Failed to set rate limit TTL for IP",
|
||||
logger.ErrorField(err),
|
||||
logger.String("client_ip", clientIP),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if rate limit exceeded (60 requests per minute)
|
||||
if count > 60 {
|
||||
// PERBAIKAN: Tambahkan log saat rate limit terlampaui
|
||||
logger.Default().WithContext(ctx).
|
||||
Warn("Rate limit exceeded for IP",
|
||||
logger.String("client_ip", clientIP),
|
||||
logger.Int64("count", count),
|
||||
)
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||
"error": "Too many requests",
|
||||
"code": "RATE_LIMIT_EXCEEDED",
|
||||
"retry_after": 60,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RateLimitByToken creates rate limiting middleware based on auth token
|
||||
func RateLimitByToken(cacheManager *cache.Manager, requestsPerMinute int) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token from Bearer format
|
||||
if len(token) > 7 && token[:7] == "Bearer " {
|
||||
token = token[7:]
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Increment rate limit counter
|
||||
count, err := cacheManager.IncrementRateLimit(ctx, "token:"+token)
|
||||
if err != nil {
|
||||
// PERBAIKAN: Gunakan logger baru
|
||||
logger.Default().WithContext(ctx).
|
||||
Error("Failed to increment token rate limit",
|
||||
logger.ErrorField(err),
|
||||
logger.String("token_prefix", token[:minLen(len(token), 10)]+"..."), // Jangan log token utuh
|
||||
)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Set TTL on first request
|
||||
if count == 1 {
|
||||
if err := cacheManager.SetRateLimit(ctx, "token:"+token, count); err != nil {
|
||||
// PERBAIKAN: Gunakan logger baru
|
||||
logger.Default().WithContext(ctx).
|
||||
Error("Failed to set token rate limit TTL",
|
||||
logger.ErrorField(err),
|
||||
logger.String("token_prefix", token[:minLen(len(token), 10)]+"..."),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if rate limit exceeded
|
||||
if count > int64(requestsPerMinute) {
|
||||
// PERBAIKAN: Tambahkan log saat rate limit token terlampaui
|
||||
logger.Default().WithContext(ctx).
|
||||
Warn("Rate limit exceeded for token",
|
||||
logger.String("token_prefix", token[:minLen(len(token), 10)]+"..."),
|
||||
logger.Int64("count", count),
|
||||
)
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||
"error": "Too many requests for this token",
|
||||
"code": "TOKEN_RATE_LIMIT_EXCEEDED",
|
||||
"retry_after": 60,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function untuk menghindari error jika token lebih pendek dari 10 karakter
|
||||
func minLen(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Variabel global untuk menyimpan limiter per-client IP/Identifier untuk memory rate limiter
|
||||
var (
|
||||
visitors = make(map[string]*rate.Limiter)
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
// getVisitor mengambil atau membuat limiter baru untuk client identifier tertentu
|
||||
func getVisitor(identifier string, r rate.Limit, b int) *rate.Limiter {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
limiter, exists := visitors[identifier]
|
||||
if !exists {
|
||||
limiter = rate.NewLimiter(r, b)
|
||||
visitors[identifier] = limiter
|
||||
}
|
||||
return limiter
|
||||
}
|
||||
|
||||
// MemoryRateLimitMiddleware membatasi jumlah request per client secara lokal di memori.
|
||||
// requestsPerSecond: jumlah hit yang diizinkan per detik.
|
||||
// burstSize: jumlah hit maksimal dalam satu waktu (burst).
|
||||
func MemoryRateLimitMiddleware(requestsPerSecond float64, burstSize int) gin.HandlerFunc {
|
||||
limit := rate.Limit(requestsPerSecond)
|
||||
return func(c *gin.Context) {
|
||||
clientIdentifier := c.ClientIP()
|
||||
|
||||
limiter := getVisitor(clientIdentifier, limit, burstSize)
|
||||
if !limiter.Allow() {
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||
"status": "error",
|
||||
"message": "Terlalu banyak permintaan ke API Satu Sehat. Silakan coba beberapa saat lagi.",
|
||||
"code": "RATE_LIMIT_EXCEEDED",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"service/internal/infrastructure/cache"
|
||||
"service/internal/infrastructure/config"
|
||||
"service/internal/infrastructure/transport/http/middleware"
|
||||
|
||||
antrolHttp "service/internal/infrastructure/transport/http/handlers/bpjs/antrol"
|
||||
aplicareHttp "service/internal/infrastructure/transport/http/handlers/bpjs/aplicare"
|
||||
apotekHttp "service/internal/infrastructure/transport/http/handlers/bpjs/apotek"
|
||||
vclaimHttp "service/internal/infrastructure/transport/http/handlers/bpjs/vclaim"
|
||||
authHttp "service/internal/infrastructure/transport/http/handlers/main/auth"
|
||||
healthHttp "service/internal/infrastructure/transport/http/handlers/main/health"
|
||||
roleHttp "service/internal/infrastructure/transport/http/handlers/main/master/roles"
|
||||
satuSehatRefHttp "service/internal/infrastructure/transport/http/handlers/satusehat/reference"
|
||||
satuSehatUsecaseHttp "service/internal/infrastructure/transport/http/handlers/satusehat/usecase"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
// ModuleHandlers menampung seluruh HTTP handler opsional dari berbagai modul.
|
||||
// Jika sebuah handler bernilai nil, maka rutenya akan otomatis diabaikan (disabled).
|
||||
type ModuleHandlers struct {
|
||||
Auth *authHttp.AuthHandler
|
||||
RolePages *roleHttp.RolPagesHandler
|
||||
RolePermission *roleHttp.RolPermissionHandler
|
||||
RoleMaster *roleHttp.RoleMasterHandler
|
||||
Sep *vclaimHttp.SepHandler
|
||||
Peserta *vclaimHttp.PesertaHandler
|
||||
Antrol *antrolHttp.AntrolHandler
|
||||
AplicareBed *aplicareHttp.BedHandler
|
||||
ApotekReference *apotekHttp.ReferenceHandler
|
||||
SatuSehatAuth *satuSehatRefHttp.AuthHandler
|
||||
SatuSehatPatient *satuSehatRefHttp.PatientHandler
|
||||
SatuSehatPractitioner *satuSehatRefHttp.PractitionerHandler
|
||||
SatuSehatOrganization *satuSehatRefHttp.OrganizationHandler
|
||||
SatuSehatLocation *satuSehatRefHttp.LocationHandler
|
||||
SatuSehatKFA *satuSehatRefHttp.KFAHandler
|
||||
SSAllergyIntolerance *satuSehatUsecaseHttp.AllergyIntoleranceHandler
|
||||
SSCarePlan *satuSehatUsecaseHttp.CarePlanHandler
|
||||
SSClinicalImpression *satuSehatUsecaseHttp.ClinicalImpressionHandler
|
||||
SSComposition *satuSehatUsecaseHttp.CompositionHandler
|
||||
SSCondition *satuSehatUsecaseHttp.ConditionHandler
|
||||
SSDiagnosticReport *satuSehatUsecaseHttp.DiagnosticReportHandler
|
||||
SSEncounter *satuSehatUsecaseHttp.EncounterHandler
|
||||
SSEpisodeOfCare *satuSehatUsecaseHttp.EpisodeOfCareHandler
|
||||
SSImagingStudy *satuSehatUsecaseHttp.ImagingStudyHandler
|
||||
SSImmunization *satuSehatUsecaseHttp.ImmunizationHandler
|
||||
SSMedication *satuSehatUsecaseHttp.MedicationHandler
|
||||
SSMedicationDispense *satuSehatUsecaseHttp.MedicationDispenseHandler
|
||||
SSMedicationRequest *satuSehatUsecaseHttp.MedicationRequestHandler
|
||||
SSMedicationStatement *satuSehatUsecaseHttp.MedicationStatementHandler
|
||||
SSObservation *satuSehatUsecaseHttp.ObservationHandler
|
||||
SSProcedure *satuSehatUsecaseHttp.ProcedureHandler
|
||||
SSQuestionnaireResponse *satuSehatUsecaseHttp.QuestionnaireResponseHandler
|
||||
SSServiceRequest *satuSehatUsecaseHttp.ServiceRequestHandler
|
||||
SSSpecimen *satuSehatUsecaseHttp.SpecimenHandler
|
||||
}
|
||||
|
||||
// SetupRoutes mendaftarkan seluruh endpoint API secara dinamis berdasarkan
|
||||
// modul-modul yang aktif (tidak nil) pada aplikasi.
|
||||
func SetupRoutes(
|
||||
engine *gin.Engine,
|
||||
cfg *config.Config,
|
||||
cacheManager *cache.Manager,
|
||||
healthHandler *healthHttp.HealthHandler,
|
||||
h *ModuleHandlers,
|
||||
) {
|
||||
// Handle 405 Method Not Allowed for better client feedback
|
||||
engine.HandleMethodNotAllowed = true
|
||||
|
||||
// Health check endpoints - using consistent http.Status constants
|
||||
engine.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"timestamp": time.Now().UTC(),
|
||||
"service": "service-general",
|
||||
"version": "1.0.0",
|
||||
})
|
||||
})
|
||||
|
||||
// Comprehensive health check with dependencies
|
||||
engine.GET("/health/complete", healthHandler.HealthCheckComplete)
|
||||
|
||||
// Database health check
|
||||
engine.GET("/health/database", healthHandler.HealthCheckDatabase)
|
||||
|
||||
// Redis/cache health check
|
||||
engine.GET("/health/cache", healthHandler.HealthCheckCache)
|
||||
|
||||
// External services health check
|
||||
engine.GET("/health/external", healthHandler.HealthCheckExternal)
|
||||
|
||||
// Minio service
|
||||
engine.GET("/health/minio", healthHandler.HealthCheckMinio)
|
||||
engine.POST("/health/minio/upload", healthHandler.TestUploadMinio)
|
||||
|
||||
// All databases health check
|
||||
// engine.GET("/health/databases", healthHandler.HealthCheckAllDatabases)
|
||||
|
||||
// Readiness check
|
||||
engine.GET("/ready", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ready",
|
||||
"timestamp": time.Now().UTC(),
|
||||
})
|
||||
})
|
||||
|
||||
// Liveness check
|
||||
engine.GET("/live", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "alive",
|
||||
"timestamp": time.Now().UTC(),
|
||||
})
|
||||
})
|
||||
|
||||
// Redirect otomatis dari /swagger ke /swagger/index.html
|
||||
engine.GET("/swagger", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/swagger/index.html")
|
||||
})
|
||||
|
||||
// Endpoint untuk Swagger UI
|
||||
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
|
||||
// API v1 routes
|
||||
v1 := engine.Group("/api/v1")
|
||||
{
|
||||
// Register auth routes (Public)
|
||||
if h.Auth != nil {
|
||||
h.Auth.RegisterRoutes(v1)
|
||||
}
|
||||
|
||||
// Private routes (Membutuhkan Autentikasi)
|
||||
protected := v1.Group("")
|
||||
protected.Use(middleware.AuthMiddleware(cfg, cacheManager))
|
||||
{
|
||||
if h.Auth != nil {
|
||||
h.Auth.RegisterProtectedRoutes(protected)
|
||||
}
|
||||
|
||||
// --- Routes Modul Master ---
|
||||
if h.RolePages != nil {
|
||||
h.RolePages.RegisterRoutes(protected)
|
||||
}
|
||||
if h.RolePermission != nil {
|
||||
h.RolePermission.RegisterRoutes(protected)
|
||||
}
|
||||
if h.RoleMaster != nil {
|
||||
h.RoleMaster.RegisterRoutes(protected)
|
||||
}
|
||||
|
||||
// --- Routes Modul BPJS VClaim ---
|
||||
if h.Sep != nil {
|
||||
h.Sep.RegisterRoutes(protected)
|
||||
}
|
||||
if h.Peserta != nil {
|
||||
h.Peserta.RegisterRoutes(protected)
|
||||
}
|
||||
|
||||
// --- Routes Modul BPJS Antrol ---
|
||||
if h.Antrol != nil {
|
||||
h.Antrol.RegisterRoutes(protected)
|
||||
}
|
||||
|
||||
// --- Routes Modul BPJS Aplicares ---
|
||||
if h.AplicareBed != nil {
|
||||
h.AplicareBed.RegisterRoutes(protected)
|
||||
}
|
||||
|
||||
// --- Routes Modul BPJS Apotek ---
|
||||
if h.ApotekReference != nil {
|
||||
h.ApotekReference.RegisterRoutes(protected)
|
||||
}
|
||||
|
||||
// --- Routes Modul Satu Sehat ---
|
||||
// Buat grup khusus untuk membatasi traffic hit API ke Kemenkes
|
||||
satuSehatGroup := protected.Group("")
|
||||
satuSehatGroup.Use(middleware.MemoryRateLimitMiddleware(5.0, 10)) // Max 5 RPS, Burst capacity 10
|
||||
{
|
||||
if h.SatuSehatAuth != nil {
|
||||
h.SatuSehatAuth.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SatuSehatPatient != nil {
|
||||
h.SatuSehatPatient.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SatuSehatPractitioner != nil {
|
||||
h.SatuSehatPractitioner.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SatuSehatOrganization != nil {
|
||||
h.SatuSehatOrganization.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SatuSehatLocation != nil {
|
||||
h.SatuSehatLocation.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SatuSehatKFA != nil {
|
||||
h.SatuSehatKFA.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSAllergyIntolerance != nil {
|
||||
h.SSAllergyIntolerance.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSCarePlan != nil {
|
||||
h.SSCarePlan.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSClinicalImpression != nil {
|
||||
h.SSClinicalImpression.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSComposition != nil {
|
||||
h.SSComposition.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSCondition != nil {
|
||||
h.SSCondition.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSDiagnosticReport != nil {
|
||||
h.SSDiagnosticReport.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSEncounter != nil {
|
||||
h.SSEncounter.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSEpisodeOfCare != nil {
|
||||
h.SSEpisodeOfCare.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSImagingStudy != nil {
|
||||
h.SSImagingStudy.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSImmunization != nil {
|
||||
h.SSImmunization.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSMedication != nil {
|
||||
h.SSMedication.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSMedicationDispense != nil {
|
||||
h.SSMedicationDispense.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSMedicationRequest != nil {
|
||||
h.SSMedicationRequest.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSMedicationStatement != nil {
|
||||
h.SSMedicationStatement.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSObservation != nil {
|
||||
h.SSObservation.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSProcedure != nil {
|
||||
h.SSProcedure.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSQuestionnaireResponse != nil {
|
||||
h.SSQuestionnaireResponse.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSServiceRequest != nil {
|
||||
h.SSServiceRequest.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
if h.SSSpecimen != nil {
|
||||
h.SSSpecimen.RegisterRoutes(satuSehatGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package servers
|
||||
|
||||
import (
|
||||
"service/internal/infrastructure/cache"
|
||||
"service/internal/infrastructure/config"
|
||||
"service/internal/infrastructure/database"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"service/internal/auth"
|
||||
antrol "service/internal/bpjs/antrol/reference"
|
||||
aplicareBed "service/internal/bpjs/aplicare/bed"
|
||||
apotekDpho "service/internal/bpjs/apotek/reference/dpho"
|
||||
apotekPoli "service/internal/bpjs/apotek/reference/poli"
|
||||
peserta "service/internal/bpjs/vclaim/peserta"
|
||||
sep "service/internal/bpjs/vclaim/sep"
|
||||
roleMaster "service/internal/master/role/master"
|
||||
rolePages "service/internal/master/role/pages"
|
||||
rolePermission "service/internal/master/role/permission"
|
||||
satuSehatAuth "service/internal/satusehat/reference/auth"
|
||||
satuSehatKfa "service/internal/satusehat/reference/kfa"
|
||||
satuSehatLocation "service/internal/satusehat/reference/location"
|
||||
satuSehatOrganization "service/internal/satusehat/reference/organization"
|
||||
satuSehatPatient "service/internal/satusehat/reference/patient"
|
||||
satuSehatPractitioner "service/internal/satusehat/reference/practitioner"
|
||||
"service/internal/satusehat/usecase/allergyintolerance"
|
||||
"service/internal/satusehat/usecase/careplan"
|
||||
clinicalimpression "service/internal/satusehat/usecase/clinicalImpression"
|
||||
"service/internal/satusehat/usecase/composition"
|
||||
"service/internal/satusehat/usecase/condition"
|
||||
"service/internal/satusehat/usecase/diagnosticreport"
|
||||
"service/internal/satusehat/usecase/encounter"
|
||||
"service/internal/satusehat/usecase/episodeofcare"
|
||||
"service/internal/satusehat/usecase/imagingstudy"
|
||||
"service/internal/satusehat/usecase/immunization"
|
||||
"service/internal/satusehat/usecase/medication"
|
||||
"service/internal/satusehat/usecase/medicationdispense"
|
||||
"service/internal/satusehat/usecase/medicationrequest"
|
||||
"service/internal/satusehat/usecase/medicationstatement"
|
||||
"service/internal/satusehat/usecase/observation"
|
||||
"service/internal/satusehat/usecase/procedure"
|
||||
"service/internal/satusehat/usecase/questionnaireresponse"
|
||||
"service/internal/satusehat/usecase/servicerequest"
|
||||
"service/internal/satusehat/usecase/specimen"
|
||||
)
|
||||
|
||||
// MasterServices menampung kumpulan service untuk domain Master Data
|
||||
type MasterServices struct {
|
||||
RolePages rolePages.Service
|
||||
RolePermission rolePermission.Service
|
||||
RoleMaster roleMaster.Service
|
||||
}
|
||||
|
||||
// VClaimServices menampung kumpulan service untuk integrasi BPJS VClaim
|
||||
type VClaimServices struct {
|
||||
Sep sep.Service
|
||||
Peserta peserta.Service
|
||||
}
|
||||
|
||||
// AntrolServices menampung kumpulan service untuk integrasi BPJS Antrean RS
|
||||
type AntrolServices struct {
|
||||
Reference antrol.Service
|
||||
}
|
||||
|
||||
// AplicaresServices menampung kumpulan service untuk integrasi BPJS Aplicares
|
||||
type AplicaresServices struct {
|
||||
Bed aplicareBed.Service
|
||||
}
|
||||
|
||||
// ApotekServices menampung kumpulan service untuk integrasi BPJS Apotek
|
||||
type ApotekServices struct {
|
||||
DPHO apotekDpho.Service
|
||||
Poli apotekPoli.Service
|
||||
}
|
||||
|
||||
// SatuSehatServices menampung kumpulan service untuk integrasi Kemenkes Satu Sehat
|
||||
type SatuSehatServices struct {
|
||||
Auth satuSehatAuth.Service
|
||||
Patient satuSehatPatient.Service
|
||||
Practitioner satuSehatPractitioner.Service
|
||||
Organization satuSehatOrganization.Service
|
||||
Location satuSehatLocation.Service
|
||||
KFA satuSehatKfa.Service
|
||||
|
||||
// Usecase Services
|
||||
AllergyIntolerance allergyintolerance.Service
|
||||
CarePlan careplan.Service
|
||||
ClinicalImpression clinicalimpression.Service
|
||||
Composition composition.Service
|
||||
Condition condition.Service
|
||||
DiagnosticReport diagnosticreport.Service
|
||||
Encounter encounter.Service
|
||||
EpisodeOfCare episodeofcare.Service
|
||||
ImagingStudy imagingstudy.Service
|
||||
Immunization immunization.Service
|
||||
Medication medication.Service
|
||||
MedicationDispense medicationdispense.Service
|
||||
MedicationRequest medicationrequest.Service
|
||||
MedicationStatement medicationstatement.Service
|
||||
Observation observation.Service
|
||||
Procedure procedure.Service
|
||||
QuestionnaireResponse questionnaireresponse.Service
|
||||
ServiceRequest servicerequest.Service
|
||||
Specimen specimen.Service
|
||||
}
|
||||
|
||||
// ServiceRegistry berfungsi sebagai Dependency Injection Container untuk transport HTTP.
|
||||
// Struktur ini mencegah membengkaknya parameter pada saat inisialisasi API.
|
||||
type ServiceRegistry struct {
|
||||
Config *config.Config
|
||||
DBManager database.Service
|
||||
PrimaryDB *gorm.DB
|
||||
CacheManager *cache.Manager
|
||||
|
||||
// Modul Aplikasi (Pisahkan berdasarkan Bounded Context)
|
||||
AuthService auth.Service
|
||||
Master *MasterServices
|
||||
VClaim *VClaimServices
|
||||
Antrol *AntrolServices
|
||||
Aplicare *AplicaresServices
|
||||
Apotek *ApotekServices
|
||||
SatuSehat *SatuSehatServices
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
// internal/infrastructure/transport/http/servers/server.go
|
||||
package servers
|
||||
|
||||
import (
|
||||
"context" // PERBAIKAN: Tambahkan import untuk graceful shutdown
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"service/internal/infrastructure/cache" // PERBAIKAN: Tambahkan import cache
|
||||
"service/internal/infrastructure/config"
|
||||
"service/internal/infrastructure/transport/http/middleware"
|
||||
"service/internal/infrastructure/transport/http/routes"
|
||||
|
||||
antrolHttp "service/internal/infrastructure/transport/http/handlers/bpjs/antrol"
|
||||
aplicareHttp "service/internal/infrastructure/transport/http/handlers/bpjs/aplicare"
|
||||
apotekHttp "service/internal/infrastructure/transport/http/handlers/bpjs/apotek"
|
||||
vclaimHttp "service/internal/infrastructure/transport/http/handlers/bpjs/vclaim"
|
||||
authHttp "service/internal/infrastructure/transport/http/handlers/main/auth"
|
||||
healthHttp "service/internal/infrastructure/transport/http/handlers/main/health"
|
||||
roleHttp "service/internal/infrastructure/transport/http/handlers/main/master/roles"
|
||||
satuSehatRefHttp "service/internal/infrastructure/transport/http/handlers/satusehat/reference"
|
||||
satuSehatUsecaseHttp "service/internal/infrastructure/transport/http/handlers/satusehat/usecase"
|
||||
|
||||
// Inisialisasi pkg
|
||||
"service/pkg/errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
engine *gin.Engine
|
||||
config *config.ServerRESTConfig
|
||||
cacheConfig cache.CacheConfig // PERBAIKAN: Tambahkan cacheConfig
|
||||
httpServer *http.Server // PERBAIKAN: Simpan instance http.Server untuk graceful shutdown
|
||||
}
|
||||
|
||||
// NewHTTPServer membuat instance server HTTP baru
|
||||
func NewHTTPServer(
|
||||
restConfig *config.ServerRESTConfig,
|
||||
registry *ServiceRegistry,
|
||||
) *Server {
|
||||
if !restConfig.Enabled {
|
||||
log.Println("HTTP server is disabled in configuration.")
|
||||
return &Server{
|
||||
config: restConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// Set mode Gin berdasarkan konfigurasi global
|
||||
if registry.Config.Server.Mode == "release" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
engine := gin.New()
|
||||
|
||||
// Middleware untuk recovery dan penanganan error konsisten.
|
||||
// Diletakkan paling awal untuk menangkap panic dari middleware/handler lain.
|
||||
engine.Use(errors.CombinedMiddleware())
|
||||
|
||||
// Middleware lainnya
|
||||
engine.Use(middleware.LoggingMiddleware())
|
||||
engine.Use(middleware.SecurityMiddleware())
|
||||
engine.Use(middleware.CORSMiddleware())
|
||||
// PERBAIKAN: Berikan cacheManager ke RateLimitMiddleware
|
||||
engine.Use(middleware.RateLimitMiddleware(registry.CacheManager))
|
||||
|
||||
// Inisialisasi ModuleHandlers secara dinamis berdasarkan fitur yang aktif
|
||||
appHandlers := &routes.ModuleHandlers{}
|
||||
|
||||
if registry.AuthService != nil {
|
||||
appHandlers.Auth = authHttp.NewAuthHandler(registry.AuthService)
|
||||
}
|
||||
|
||||
// Inisialisasi Handlers Modul Master
|
||||
if registry.Master != nil {
|
||||
if registry.Master.RolePages != nil {
|
||||
appHandlers.RolePages = roleHttp.NewRolPagesHandler(registry.Master.RolePages)
|
||||
}
|
||||
if registry.Master.RolePermission != nil {
|
||||
appHandlers.RolePermission = roleHttp.NewRolPermissionHandler(registry.Master.RolePermission)
|
||||
}
|
||||
if registry.Master.RoleMaster != nil {
|
||||
appHandlers.RoleMaster = roleHttp.NewRoleMasterHandler(registry.Master.RoleMaster)
|
||||
}
|
||||
}
|
||||
|
||||
// Inisialisasi Handlers Modul VClaim
|
||||
if registry.VClaim != nil {
|
||||
if registry.VClaim.Sep != nil {
|
||||
appHandlers.Sep = vclaimHttp.NewSepHandler(registry.VClaim.Sep)
|
||||
}
|
||||
if registry.VClaim.Peserta != nil {
|
||||
appHandlers.Peserta = vclaimHttp.NewPesertaHandler(registry.VClaim.Peserta)
|
||||
}
|
||||
}
|
||||
|
||||
// Inisialisasi Handlers Modul Antrol
|
||||
if registry.Antrol != nil {
|
||||
if registry.Antrol.Reference != nil {
|
||||
appHandlers.Antrol = antrolHttp.NewAntrolHandler(registry.Antrol.Reference)
|
||||
}
|
||||
}
|
||||
|
||||
// Inisialisasi Handlers Modul Aplicares
|
||||
if registry.Aplicare != nil {
|
||||
if registry.Aplicare.Bed != nil {
|
||||
appHandlers.AplicareBed = aplicareHttp.NewBedHandler(registry.Aplicare.Bed)
|
||||
}
|
||||
}
|
||||
|
||||
// Inisialisasi Handlers Modul Apotek
|
||||
if registry.Apotek != nil {
|
||||
if registry.Apotek.DPHO != nil && registry.Apotek.Poli != nil {
|
||||
appHandlers.ApotekReference = apotekHttp.NewReferenceHandler(registry.Apotek.DPHO, registry.Apotek.Poli)
|
||||
}
|
||||
}
|
||||
|
||||
// Inisialisasi Handlers Modul Satu Sehat
|
||||
if registry.SatuSehat != nil {
|
||||
if registry.SatuSehat.Auth != nil {
|
||||
appHandlers.SatuSehatAuth = satuSehatRefHttp.NewAuthHandler(registry.SatuSehat.Auth)
|
||||
}
|
||||
if registry.SatuSehat.Patient != nil {
|
||||
appHandlers.SatuSehatPatient = satuSehatRefHttp.NewPatientHandler(registry.SatuSehat.Patient)
|
||||
}
|
||||
if registry.SatuSehat.Practitioner != nil {
|
||||
appHandlers.SatuSehatPractitioner = satuSehatRefHttp.NewPractitionerHandler(registry.SatuSehat.Practitioner)
|
||||
}
|
||||
if registry.SatuSehat.Organization != nil {
|
||||
appHandlers.SatuSehatOrganization = satuSehatRefHttp.NewOrganizationHandler(registry.SatuSehat.Organization)
|
||||
}
|
||||
if registry.SatuSehat.Location != nil {
|
||||
appHandlers.SatuSehatLocation = satuSehatRefHttp.NewLocationHandler(registry.SatuSehat.Location)
|
||||
}
|
||||
if registry.SatuSehat.KFA != nil {
|
||||
appHandlers.SatuSehatKFA = satuSehatRefHttp.NewKFAHandler(registry.SatuSehat.KFA)
|
||||
}
|
||||
|
||||
// Inisialisasi Handlers Modul Satu Sehat Usecase
|
||||
if registry.SatuSehat.AllergyIntolerance != nil {
|
||||
appHandlers.SSAllergyIntolerance = satuSehatUsecaseHttp.NewAllergyIntoleranceHandler(registry.SatuSehat.AllergyIntolerance)
|
||||
}
|
||||
if registry.SatuSehat.CarePlan != nil {
|
||||
appHandlers.SSCarePlan = satuSehatUsecaseHttp.NewCarePlanHandler(registry.SatuSehat.CarePlan)
|
||||
}
|
||||
if registry.SatuSehat.ClinicalImpression != nil {
|
||||
appHandlers.SSClinicalImpression = satuSehatUsecaseHttp.NewClinicalImpressionHandler(registry.SatuSehat.ClinicalImpression)
|
||||
}
|
||||
if registry.SatuSehat.Composition != nil {
|
||||
appHandlers.SSComposition = satuSehatUsecaseHttp.NewCompositionHandler(registry.SatuSehat.Composition)
|
||||
}
|
||||
if registry.SatuSehat.Condition != nil {
|
||||
appHandlers.SSCondition = satuSehatUsecaseHttp.NewConditionHandler(registry.SatuSehat.Condition)
|
||||
}
|
||||
if registry.SatuSehat.DiagnosticReport != nil {
|
||||
appHandlers.SSDiagnosticReport = satuSehatUsecaseHttp.NewDiagnosticReportHandler(registry.SatuSehat.DiagnosticReport)
|
||||
}
|
||||
if registry.SatuSehat.Encounter != nil {
|
||||
appHandlers.SSEncounter = satuSehatUsecaseHttp.NewEncounterHandler(registry.SatuSehat.Encounter)
|
||||
}
|
||||
if registry.SatuSehat.EpisodeOfCare != nil {
|
||||
appHandlers.SSEpisodeOfCare = satuSehatUsecaseHttp.NewEpisodeOfCareHandler(registry.SatuSehat.EpisodeOfCare)
|
||||
}
|
||||
if registry.SatuSehat.ImagingStudy != nil {
|
||||
appHandlers.SSImagingStudy = satuSehatUsecaseHttp.NewImagingStudyHandler(registry.SatuSehat.ImagingStudy)
|
||||
}
|
||||
if registry.SatuSehat.Immunization != nil {
|
||||
appHandlers.SSImmunization = satuSehatUsecaseHttp.NewImmunizationHandler(registry.SatuSehat.Immunization)
|
||||
}
|
||||
if registry.SatuSehat.Medication != nil {
|
||||
appHandlers.SSMedication = satuSehatUsecaseHttp.NewMedicationHandler(registry.SatuSehat.Medication)
|
||||
}
|
||||
if registry.SatuSehat.MedicationDispense != nil {
|
||||
appHandlers.SSMedicationDispense = satuSehatUsecaseHttp.NewMedicationDispenseHandler(registry.SatuSehat.MedicationDispense)
|
||||
}
|
||||
if registry.SatuSehat.MedicationRequest != nil {
|
||||
appHandlers.SSMedicationRequest = satuSehatUsecaseHttp.NewMedicationRequestHandler(registry.SatuSehat.MedicationRequest)
|
||||
}
|
||||
if registry.SatuSehat.MedicationStatement != nil {
|
||||
appHandlers.SSMedicationStatement = satuSehatUsecaseHttp.NewMedicationStatementHandler(registry.SatuSehat.MedicationStatement)
|
||||
}
|
||||
if registry.SatuSehat.Observation != nil {
|
||||
appHandlers.SSObservation = satuSehatUsecaseHttp.NewObservationHandler(registry.SatuSehat.Observation)
|
||||
}
|
||||
if registry.SatuSehat.Procedure != nil {
|
||||
appHandlers.SSProcedure = satuSehatUsecaseHttp.NewProcedureHandler(registry.SatuSehat.Procedure)
|
||||
}
|
||||
if registry.SatuSehat.QuestionnaireResponse != nil {
|
||||
appHandlers.SSQuestionnaireResponse = satuSehatUsecaseHttp.NewQuestionnaireResponseHandler(registry.SatuSehat.QuestionnaireResponse)
|
||||
}
|
||||
if registry.SatuSehat.ServiceRequest != nil {
|
||||
appHandlers.SSServiceRequest = satuSehatUsecaseHttp.NewServiceRequestHandler(registry.SatuSehat.ServiceRequest)
|
||||
}
|
||||
if registry.SatuSehat.Specimen != nil {
|
||||
appHandlers.SSSpecimen = satuSehatUsecaseHttp.NewSpecimenHandler(registry.SatuSehat.Specimen)
|
||||
}
|
||||
}
|
||||
|
||||
// Inisialisasi Health Handler dengan dependensi yang benar
|
||||
// Gunakan constructor baru yang menerima cache manager
|
||||
healthHandler := healthHttp.NewHealthHandlerWithCache(registry.PrimaryDB, registry.CacheManager, registry.Config, registry.DBManager)
|
||||
|
||||
// Setup routes menggunakan handler yang sudah diinisialisasi
|
||||
routes.SetupRoutes(
|
||||
engine,
|
||||
registry.Config,
|
||||
registry.CacheManager,
|
||||
healthHandler,
|
||||
appHandlers,
|
||||
)
|
||||
|
||||
return &Server{
|
||||
engine: engine,
|
||||
config: restConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterSwagger mendaftarkan dokumentasi Swagger UI
|
||||
func (s *Server) RegisterSwagger() {
|
||||
if s.engine == nil {
|
||||
log.Println("Cannot register Swagger: engine is nil (server disabled)")
|
||||
return
|
||||
}
|
||||
s.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
}
|
||||
|
||||
// Start menjalankan server HTTP
|
||||
func (s *Server) Start(globalConfig *config.ServerConfig) error {
|
||||
if !s.config.Enabled {
|
||||
log.Println("HTTP server is disabled, not starting.")
|
||||
return nil
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf(":%d", s.config.Port)
|
||||
log.Printf("Starting HTTP server on %s", addr)
|
||||
|
||||
// PERBAIKAN: Simpan instance server ke struct
|
||||
s.httpServer = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: s.engine,
|
||||
ReadTimeout: time.Duration(globalConfig.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(globalConfig.WriteTimeout) * time.Second,
|
||||
IdleTimeout: 60 * time.Second, // Good practice to have an idle timeout
|
||||
}
|
||||
|
||||
return s.httpServer.ListenAndServe()
|
||||
}
|
||||
|
||||
// PERBAIKAN: Tambahkan method untuk graceful shutdown
|
||||
// Shutdown memberhentikan server HTTP dengan graceful shutdown
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
if s.httpServer == nil {
|
||||
return nil
|
||||
}
|
||||
log.Println("Shutting down HTTP server...")
|
||||
return s.httpServer.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// GetEngine mengembalikan instance Gin engine (terutama untuk testing)
|
||||
func (s *Server) GetEngine() *gin.Engine {
|
||||
return s.engine
|
||||
}
|
||||
Reference in New Issue
Block a user