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

@@ -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 ""
}