first commit

This commit is contained in:
meninjar
2026-04-14 01:23:34 +00:00
commit edfaa886ff
443 changed files with 1245931 additions and 0 deletions

No files matched your search

@@ -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(&params); 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)
}
}
@@ -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(&params); 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)
}
}
@@ -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(&params); 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)
}
}
@@ -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(&params); 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)
}
@@ -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(&params); 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)
}
}