351 lines
9.4 KiB
Go
351 lines
9.4 KiB
Go
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 ""
|
|
}
|