Perubahan tool dan dokumentasi

This commit is contained in:
2025-08-22 15:49:30 +07:00
parent ce7d12f20c
commit 9838c48eab
14 changed files with 4077 additions and 329 deletions

View File

@@ -19,6 +19,7 @@ type Config struct {
ReadReplicas map[string][]DatabaseConfig // For read replicas
Keycloak KeycloakConfig
Bpjs BpjsConfig
SatuSehat SatuSehatConfig
}
type ServerConfig struct {
@@ -58,6 +59,18 @@ type BpjsConfig struct {
Timeout time.Duration `json:"timeout"`
}
type SatuSehatConfig struct {
OrgID string `json:"org_id"`
FasyakesID string `json:"fasyakes_id"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
AuthURL string `json:"auth_url"`
BaseURL string `json:"base_url"`
ConsentURL string `json:"consent_url"`
KFAURL string `json:"kfa_url"`
Timeout time.Duration `json:"timeout"`
}
// SetHeader generates required headers for BPJS VClaim API
func (cfg BpjsConfig) SetHeader() (string, string, string, string, string) {
timenow := time.Now().UTC()
@@ -117,6 +130,17 @@ func LoadConfig() *Config {
SecretKey: getEnv("BPJS_SECRETKEY", ""),
Timeout: parseDuration(getEnv("BPJS_TIMEOUT", "30s")),
},
SatuSehat: SatuSehatConfig{
OrgID: getEnv("BRIDGING_SATUSEHAT_ORG_ID", ""),
FasyakesID: getEnv("BRIDGING_SATUSEHAT_FASYAKES_ID", ""),
ClientID: getEnv("BRIDGING_SATUSEHAT_CLIENT_ID", ""),
ClientSecret: getEnv("BRIDGING_SATUSEHAT_CLIENT_SECRET", ""),
AuthURL: getEnv("BRIDGING_SATUSEHAT_AUTH_URL", "https://api-satusehat.kemkes.go.id/oauth2/v1"),
BaseURL: getEnv("BRIDGING_SATUSEHAT_BASE_URL", "https://api-satusehat.kemkes.go.id/fhir-r4/v1"),
ConsentURL: getEnv("BRIDGING_SATUSEHAT_CONSENT_URL", "https://api-satusehat.dto.kemkes.go.id/consent/v1"),
KFAURL: getEnv("BRIDGING_SATUSEHAT_KFA_URL", "https://api-satusehat.kemkes.go.id/kfa-v2"),
Timeout: parseDuration(getEnv("BRIDGING_SATUSEHAT_TIMEOUT", "30s")),
},
}
// Load database configurations
@@ -622,5 +646,25 @@ func (c *Config) Validate() error {
}
}
// Validate SatuSehat configuration
if c.SatuSehat.OrgID == "" {
log.Fatal("SatuSehat Organization ID is required")
}
if c.SatuSehat.FasyakesID == "" {
log.Fatal("SatuSehat Fasyankes ID is required")
}
if c.SatuSehat.ClientID == "" {
log.Fatal("SatuSehat Client ID is required")
}
if c.SatuSehat.ClientSecret == "" {
log.Fatal("SatuSehat Client Secret is required")
}
if c.SatuSehat.AuthURL == "" {
log.Fatal("SatuSehat Auth URL is required")
}
if c.SatuSehat.BaseURL == "" {
log.Fatal("SatuSehat Base URL is required")
}
return nil
}

View File

@@ -74,8 +74,8 @@ func NewRetribusiHandler() *RetribusiHandler {
// @Param dinas query string false "Filter by dinas"
// @Param search query string false "Search in multiple fields"
// @Success 200 {object} modelsretribusi.RetribusiGetResponse "Success response"
// @Failure 400 {object} modelsretribusi.ErrorResponse "Bad request"
// @Failure 500 {object} modelsretribusi.ErrorResponse "Internal server error"
// @Failure 400 {object} models.ErrorResponse "Bad request"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis [get]
func (h *RetribusiHandler) GetRetribusi(c *gin.Context) {
// Parse pagination parameters
@@ -186,9 +186,9 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) {
// @Produce json
// @Param id path string true "Retribusi ID (UUID)"
// @Success 200 {object} modelsretribusi.RetribusiGetByIDResponse "Success response"
// @Failure 400 {object} modelsretribusi.ErrorResponse "Invalid ID format"
// @Failure 404 {object} modelsretribusi.ErrorResponse "Retribusi not found"
// @Failure 500 {object} modelsretribusi.ErrorResponse "Internal server error"
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
// @Failure 404 {object} models.ErrorResponse "Retribusi not found"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusi/{id} [get]
func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) {
id := c.Param("id")
@@ -238,8 +238,8 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) {
// @Param limit query int false "Limit" default(10)
// @Param offset query int false "Offset" default(0)
// @Success 200 {object} modelsretribusi.RetribusiGetResponse "Success response"
// @Failure 400 {object} modelsretribusi.ErrorResponse "Bad request"
// @Failure 500 {object} modelsretribusi.ErrorResponse "Internal server error"
// @Failure 400 {object} models.ErrorResponse "Bad request"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis/dynamic [get]
func (h *RetribusiHandler) GetRetribusiDynamic(c *gin.Context) {
// Parse query parameters
@@ -501,8 +501,8 @@ func (h *RetribusiHandler) SearchRetribusiAdvanced(c *gin.Context) {
// @Produce json
// @Param request body modelsretribusi.RetribusiCreateRequest true "Retribusi creation request"
// @Success 201 {object} modelsretribusi.RetribusiCreateResponse "Retribusi created successfully"
// @Failure 400 {object} modelsretribusi.ErrorResponse "Bad request or validation error"
// @Failure 500 {object} modelsretribusi.ErrorResponse "Internal server error"
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis [post]
func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) {
var req modelsretribusi.RetribusiCreateRequest
@@ -556,9 +556,9 @@ func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) {
// @Param id path string true "Retribusi ID (UUID)"
// @Param request body modelsretribusi.RetribusiUpdateRequest true "Retribusi update request"
// @Success 200 {object} modelsretribusi.RetribusiUpdateResponse "Retribusi updated successfully"
// @Failure 400 {object} modelsretribusi.ErrorResponse "Bad request or validation error"
// @Failure 404 {object} modelsretribusi.ErrorResponse "Retribusi not found"
// @Failure 500 {object} modelsretribusi.ErrorResponse "Internal server error"
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
// @Failure 404 {object} models.ErrorResponse "Retribusi not found"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusi/{id} [put]
func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) {
id := c.Param("id")
@@ -619,9 +619,9 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) {
// @Produce json
// @Param id path string true "Retribusi ID (UUID)"
// @Success 200 {object} modelsretribusi.RetribusiDeleteResponse "Retribusi deleted successfully"
// @Failure 400 {object} modelsretribusi.ErrorResponse "Invalid ID format"
// @Failure 404 {object} modelsretribusi.ErrorResponse "Retribusi not found"
// @Failure 500 {object} modelsretribusi.ErrorResponse "Internal server error"
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
// @Failure 404 {object} models.ErrorResponse "Retribusi not found"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusi/{id} [delete]
func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) {
id := c.Param("id")
@@ -666,8 +666,8 @@ func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) {
// @Accept json
// @Produce json
// @Param status query string false "Filter statistics by status"
// @Success 200 {object} modelsretribusi.AggregateData "Statistics data"
// @Failure 500 {object} modelsretribusi.ErrorResponse "Internal server error"
// @Success 200 {object} models.AggregateData "Statistics data"
// @Failure 500 {object} models.ErrorResponse "Internal server error"
// @Router /api/v1/retribusis/stats [get]
func (h *RetribusiHandler) GetRetribusiStats(c *gin.Context) {
dbConn, err := h.db.GetDB("postgres_satudata")

View File

@@ -0,0 +1,192 @@
package satusehat
import (
"net/http"
"api-service/internal/services/satusehat"
"github.com/gin-gonic/gin"
)
type PatientHandler struct {
service *satusehat.SatuSehatService
}
func NewPatientHandler(service *satusehat.SatuSehatService) *PatientHandler {
return &PatientHandler{
service: service,
}
}
// SearchPatientByNIK godoc
// @Summary Search patient by NIK
// @Description Search patient data from SatuSehat by National Identity Number (NIK)
// @Tags SatuSehat
// @Accept json
// @Produce json
// @Param nik query string true "National Identity Number (NIK)"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Failure 500 {object} map[string]interface{}
// @Router /satusehat/patient/search/nik [get]
func (h *PatientHandler) SearchPatientByNIK(c *gin.Context) {
nik := c.Query("nik")
if nik == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Bad Request",
"message": "NIK parameter is required",
})
return
}
patientResp, err := h.service.SearchPatientByNIK(c.Request.Context(), nik)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"message": err.Error(),
})
return
}
patientInfo, err := satusehat.ExtractPatientInfo(patientResp)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"error": "Not Found",
"message": "Patient not found",
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": patientInfo,
})
}
// SearchPatientByName godoc
// @Summary Search patient by name
// @Description Search patient data from SatuSehat by name
// @Tags SatuSehat
// @Accept json
// @Produce json
// @Param name query string true "Patient name"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Failure 500 {object} map[string]interface{}
// @Router /satusehat/patient/search/name [get]
func (h *PatientHandler) SearchPatientByName(c *gin.Context) {
name := c.Query("name")
if name == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Bad Request",
"message": "Name parameter is required",
})
return
}
patientResp, err := h.service.SearchPatientByName(c.Request.Context(), name)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"message": err.Error(),
})
return
}
if patientResp == nil || len(patientResp.Entry) == 0 {
c.JSON(http.StatusNotFound, gin.H{
"error": "Not Found",
"message": "Patient not found",
})
return
}
// Return all found patients
var patients []map[string]interface{}
for _, entry := range patientResp.Entry {
patientInfo := map[string]interface{}{
"id": entry.Resource.ID,
"name": satusehat.ExtractPatientName(entry.Resource.Name),
"nik": satusehat.ExtractNIK(entry.Resource.Identifier),
"gender": entry.Resource.Gender,
"birthDate": entry.Resource.BirthDate,
"address": satusehat.ExtractAddress(entry.Resource.Address),
"phone": satusehat.ExtractPhone(entry.Resource.Telecom),
"lastUpdated": entry.Resource.Meta.LastUpdated,
}
patients = append(patients, patientInfo)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": patients,
"total": len(patients),
})
}
// CreatePatient godoc
// @Summary Create new patient
// @Description Create new patient data in SatuSehat
// @Tags SatuSehat
// @Accept json
// @Produce json
// @Param patient body map[string]interface{} true "Patient data"
// @Success 201 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Failure 500 {object} map[string]interface{}
// @Router /satusehat/patient [post]
func (h *PatientHandler) CreatePatient(c *gin.Context) {
var patientData map[string]interface{}
if err := c.ShouldBindJSON(&patientData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Bad Request",
"message": "Invalid JSON format",
})
return
}
response, err := h.service.CreatePatient(c.Request.Context(), patientData)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"message": err.Error(),
})
return
}
c.JSON(http.StatusCreated, gin.H{
"success": true,
"data": response,
})
}
// GetAccessToken godoc
// @Summary Get access token
// @Description Get SatuSehat access token
// @Tags SatuSehat
// @Accept json
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Failure 500 {object} map[string]interface{}
// @Router /satusehat/token [get]
func (h *PatientHandler) GetAccessToken(c *gin.Context) {
token, err := h.service.GetAccessToken(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": map[string]interface{}{
"access_token": token.AccessToken,
"token_type": token.TokenType,
"expires_in": token.ExpiresIn,
"scope": token.Scope,
"issued_at": token.IssuedAt,
},
})
}

View File

@@ -5,8 +5,10 @@ import (
authHandlers "api-service/internal/handlers/auth"
bpjsPesertaHandlers "api-service/internal/handlers/bpjs/reference"
retribusiHandlers "api-service/internal/handlers/retribusi"
satusehatHandlers "api-service/internal/handlers/satusehat"
"api-service/internal/middleware"
services "api-service/internal/services/auth"
satusehatServices "api-service/internal/services/satusehat"
"api-service/pkg/logger"
"github.com/gin-gonic/gin"
@@ -32,6 +34,12 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
logger.Fatal("Failed to initialize auth service")
}
// Initialize SatuSehat service
satusehatService := satusehatServices.NewSatuSehatService(&cfg.SatuSehat)
if satusehatService == nil {
logger.Fatal("Failed to initialize SatuSehat service")
}
// Swagger UI route
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
@@ -59,6 +67,16 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs)
v1.GET("/bpjs/peserta/nik/:nik/tglSEP/:tglSEP", bpjsPesertaHandler.GetPesertaByNIK)
// SatuSehat endpoints
satusehatPatientHandler := satusehatHandlers.NewPatientHandler(satusehatService)
satusehatGroup := v1.Group("/satusehat")
{
satusehatGroup.GET("/patient/search/nik", satusehatPatientHandler.SearchPatientByNIK)
satusehatGroup.GET("/patient/search/name", satusehatPatientHandler.SearchPatientByName)
satusehatGroup.POST("/patient", satusehatPatientHandler.CreatePatient)
satusehatGroup.GET("/token", satusehatPatientHandler.GetAccessToken)
}
// ============= PUBLISHED ROUTES ===============================================
// // Retribusi endpoints

View File

@@ -0,0 +1,350 @@
package satusehat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"api-service/internal/config"
)
type SatuSehatService struct {
config *config.SatuSehatConfig
client *http.Client
token *TokenResponse
}
type TokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
IssuedAt time.Time
}
type PatientResponse struct {
ResourceType string `json:"resourceType"`
ID string `json:"id"`
Meta struct {
VersionID string `json:"versionId"`
LastUpdated string `json:"lastUpdated"`
} `json:"meta"`
Type string `json:"type"`
Total int `json:"total"`
Link []Link `json:"link"`
Entry []Entry `json:"entry"`
}
type Link struct {
Relation string `json:"relation"`
URL string `json:"url"`
}
type Entry struct {
FullURL string `json:"fullUrl"`
Resource struct {
ResourceType string `json:"resourceType"`
ID string `json:"id"`
Meta struct {
VersionID string `json:"versionId"`
LastUpdated string `json:"lastUpdated"`
Profile []string `json:"profile"`
} `json:"meta"`
Identifier []Identifier `json:"identifier"`
Name []Name `json:"name"`
Telecom []Telecom `json:"telecom"`
Gender string `json:"gender"`
BirthDate string `json:"birthDate"`
Deceased bool `json:"deceasedBoolean"`
Address []Address `json:"address"`
MaritalStatus struct {
Coding []Coding `json:"coding"`
} `json:"maritalStatus"`
MultipleBirth bool `json:"multipleBirthBoolean"`
Contact []Contact `json:"contact"`
Communication []Communication `json:"communication"`
Extension []Extension `json:"extension"`
} `json:"resource"`
Search struct {
Mode string `json:"mode"`
} `json:"search"`
}
type Identifier struct {
System string `json:"system"`
Value string `json:"value"`
Use string `json:"use,omitempty"`
}
type Name struct {
Use string `json:"use"`
Text string `json:"text"`
Family string `json:"family"`
Given []string `json:"given"`
}
type Telecom struct {
System string `json:"system"`
Value string `json:"value"`
Use string `json:"use,omitempty"`
}
type Address struct {
Use string `json:"use"`
Type string `json:"type"`
Line []string `json:"line"`
City string `json:"city"`
PostalCode string `json:"postalCode"`
Country string `json:"country"`
Extension []Extension `json:"extension"`
}
type Coding struct {
System string `json:"system"`
Code string `json:"code"`
Display string `json:"display"`
}
type Contact struct {
Relationship []Coding `json:"relationship"`
Name Name `json:"name"`
Telecom []Telecom `json:"telecom"`
Address Address `json:"address"`
Gender string `json:"gender"`
}
type Communication struct {
Language Coding `json:"language"`
Preferred bool `json:"preferred"`
}
type Extension struct {
URL string `json:"url"`
ValueAddress Address `json:"valueAddress,omitempty"`
ValueCode string `json:"valueCode,omitempty"`
}
func NewSatuSehatService(cfg *config.SatuSehatConfig) *SatuSehatService {
return &SatuSehatService{
config: cfg,
client: &http.Client{
Timeout: cfg.Timeout,
},
}
}
func (s *SatuSehatService) GetAccessToken(ctx context.Context) (*TokenResponse, error) {
// Check if we have a valid token
if s.token != nil && time.Since(s.token.IssuedAt) < time.Duration(s.token.ExpiresIn-60)*time.Second {
return s.token, nil
}
url := fmt.Sprintf("%s/accesstoken?grant_type=client_credentials", s.config.AuthURL)
req, err := http.NewRequestWithContext(ctx, "POST", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
req.SetBasicAuth(s.config.ClientID, s.config.ClientSecret)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := s.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to get access token: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get access token, status: %s", resp.Status)
}
var tokenResp TokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return nil, fmt.Errorf("failed to decode token response: %v", err)
}
tokenResp.IssuedAt = time.Now()
s.token = &tokenResp
return &tokenResp, nil
}
func (s *SatuSehatService) SearchPatientByNIK(ctx context.Context, nik string) (*PatientResponse, error) {
token, err := s.GetAccessToken(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get access token: %v", err)
}
url := fmt.Sprintf("%s/Patient?identifier=https://fhir.kemkes.go.id/id/nik|%s", s.config.BaseURL, nik)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
req.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to search patient: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to search patient, status: %s", resp.Status)
}
var patientResp PatientResponse
if err := json.NewDecoder(resp.Body).Decode(&patientResp); err != nil {
return nil, fmt.Errorf("failed to decode patient response: %v", err)
}
return &patientResp, nil
}
func (s *SatuSehatService) SearchPatientByName(ctx context.Context, name string) (*PatientResponse, error) {
token, err := s.GetAccessToken(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get access token: %v", err)
}
url := fmt.Sprintf("%s/Patient?name=%s", s.config.BaseURL, name)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
req.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to search patient: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to search patient, status: %s", resp.Status)
}
var patientResp PatientResponse
if err := json.NewDecoder(resp.Body).Decode(&patientResp); err != nil {
return nil, fmt.Errorf("failed to decode patient response: %v", err)
}
return &patientResp, nil
}
func (s *SatuSehatService) CreatePatient(ctx context.Context, patientData map[string]interface{}) (map[string]interface{}, error) {
token, err := s.GetAccessToken(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get access token: %v", err)
}
url := fmt.Sprintf("%s/Patient", s.config.BaseURL)
patientData["resourceType"] = "Patient"
jsonData, err := json.Marshal(patientData)
if err != nil {
return nil, fmt.Errorf("failed to marshal patient data: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(jsonData)))
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
req.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to create patient: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("failed to create patient, status: %s", resp.Status)
}
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode response: %v", err)
}
return response, nil
}
// Helper function to extract patient information
func ExtractPatientInfo(patientResp *PatientResponse) (map[string]interface{}, error) {
if patientResp == nil || len(patientResp.Entry) == 0 {
return nil, fmt.Errorf("no patient data found")
}
entry := patientResp.Entry[0]
resource := entry.Resource
patientInfo := map[string]interface{}{
"id": resource.ID,
"name": ExtractPatientName(resource.Name),
"nik": ExtractNIK(resource.Identifier),
"gender": resource.Gender,
"birthDate": resource.BirthDate,
"address": ExtractAddress(resource.Address),
"phone": ExtractPhone(resource.Telecom),
"lastUpdated": resource.Meta.LastUpdated,
}
return patientInfo, nil
}
func ExtractPatientName(names []Name) string {
for _, name := range names {
if name.Use == "official" || name.Text != "" {
if name.Text != "" {
return name.Text
}
return fmt.Sprintf("%s %s", strings.Join(name.Given, " "), name.Family)
}
}
return ""
}
func ExtractNIK(identifiers []Identifier) string {
for _, ident := range identifiers {
if ident.System == "https://fhir.kemkes.go.id/id/nik" {
return ident.Value
}
}
return ""
}
func ExtractAddress(addresses []Address) map[string]interface{} {
if len(addresses) == 0 {
return nil
}
addr := addresses[0]
return map[string]interface{}{
"line": strings.Join(addr.Line, ", "),
"city": addr.City,
"postalCode": addr.PostalCode,
"country": addr.Country,
}
}
func ExtractPhone(telecoms []Telecom) string {
for _, telecom := range telecoms {
if telecom.System == "phone" {
return telecom.Value
}
}
return ""
}