penambahan All case Satu sehat
This commit is contained in:
@@ -6,7 +6,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -88,6 +90,16 @@ func (c *client) GetAccessToken(ctx context.Context) (map[string]interface{}, er
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Default().Error("SatuSehat Auth Error", logger.Int("status", resp.StatusCode), logger.String("response", string(respBody)))
|
||||
|
||||
// Coba parse response body sebagai FHIR OperationOutcome untuk meneruskan status code asli ke client
|
||||
var outcome map[string]interface{}
|
||||
if err := json.Unmarshal(respBody, &outcome); err == nil && outcome["resourceType"] == "OperationOutcome" {
|
||||
return nil, &ErrorOperationOutcome{
|
||||
StatusCode: resp.StatusCode,
|
||||
Outcome: outcome,
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("SatuSehat Auth failed with status %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
@@ -153,21 +165,35 @@ func (c *client) do(ctx context.Context, baseURL, method, endpoint string, body
|
||||
endpointPath := strings.TrimLeft(endpoint, "/")
|
||||
targetURL := fmt.Sprintf("%s/%s", baseURL, endpointPath)
|
||||
|
||||
// Marshal body terlebih dahulu agar bisa digunakan berulang kali jika butuh retry
|
||||
var jsonBody []byte
|
||||
// Identifikasi dan format body berdasarkan tipenya (string/plaintext atau struct/JSON)
|
||||
var reqBody []byte
|
||||
var contentType string
|
||||
|
||||
if body != nil {
|
||||
var err error
|
||||
jsonBody, err = json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
switch v := body.(type) {
|
||||
case string:
|
||||
// Jika body berupa string murni (seperti encrypted payload KYC), jangan di-marshal JSON
|
||||
reqBody = []byte(v)
|
||||
contentType = "text/plain"
|
||||
default:
|
||||
var err error
|
||||
reqBody, err = json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
if method == "PATCH" {
|
||||
contentType = "application/json-patch+json"
|
||||
} else {
|
||||
contentType = "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function untuk merakit ulang request (sangat berguna untuk skenario Retry)
|
||||
buildReq := func(accessToken string) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
if jsonBody != nil {
|
||||
bodyReader = bytes.NewBuffer(jsonBody)
|
||||
if reqBody != nil {
|
||||
bodyReader = bytes.NewBuffer(reqBody)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, method, targetURL, bodyReader)
|
||||
if err != nil {
|
||||
@@ -175,12 +201,8 @@ func (c *client) do(ctx context.Context, baseURL, method, endpoint string, body
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if jsonBody != nil {
|
||||
if method == "PATCH" {
|
||||
req.Header.Set("Content-Type", "application/json-patch+json")
|
||||
} else {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
if reqBody != nil {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
@@ -272,3 +294,83 @@ func (c *client) DoKFA(ctx context.Context, method, endpoint string, body interf
|
||||
func (c *client) DoConsent(ctx context.Context, method, endpoint string, body interface{}) ([]byte, error) {
|
||||
return c.do(ctx, c.cfg.ConsentURL, method, endpoint, body)
|
||||
}
|
||||
|
||||
func (c *client) DoKYC(ctx context.Context, method, endpoint string, body interface{}) ([]byte, error) {
|
||||
return c.do(ctx, c.cfg.KYCURL, method, endpoint, body)
|
||||
}
|
||||
|
||||
// UploadDICOM mengunggah file biner .dcm langsung ke SatuSehat STOW-RS endpoint
|
||||
func (c *client) UploadDICOM(ctx context.Context, dicomBytes []byte) ([]byte, error) {
|
||||
if !c.cfg.Enabled {
|
||||
return nil, fmt.Errorf("SatuSehat integration is disabled")
|
||||
}
|
||||
|
||||
authData, err := c.GetAccessToken(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get SatuSehat access token: %w", err)
|
||||
}
|
||||
token, _ := authData["access_token"].(string)
|
||||
|
||||
// 1. Buat body multipart
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
// 2. Buat header part khusus untuk DICOM (Kemenkes mewajibkan application/dicom)
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set("Content-Type", "application/dicom")
|
||||
|
||||
part, err := writer.CreatePart(h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gagal membuat multipart frame: %w", err)
|
||||
}
|
||||
|
||||
// 3. Tulis biner DICOM ke dalam part
|
||||
if _, err := part.Write(dicomBytes); err != nil {
|
||||
return nil, fmt.Errorf("gagal menulis biner DICOM: %w", err)
|
||||
}
|
||||
writer.Close()
|
||||
|
||||
// 4. Siapkan HTTP Request
|
||||
// NOTE: Content-Type untuk STOW-RS BUKAN multipart/form-data, TAPI multipart/related
|
||||
contentType := fmt.Sprintf(`multipart/related; type="application/dicom"; boundary=%s`, writer.Boundary())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", c.cfg.DicomURL, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create DICOM request: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.Header.Set("Accept", "application/dicom+json") // Format balasan Kemenkes
|
||||
|
||||
logger.Default().Info("🛫 Mengirim DICOM ke SatuSehat", logger.String("target_url", c.cfg.DicomURL))
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("http request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
logger.Default().Error("SatuSehat DICOM API Error",
|
||||
logger.Int("status_code", resp.StatusCode),
|
||||
logger.String("response", string(respBody)),
|
||||
)
|
||||
|
||||
var outcome map[string]interface{}
|
||||
if err := json.Unmarshal(respBody, &outcome); err == nil && outcome["resourceType"] == "OperationOutcome" {
|
||||
return nil, &ErrorOperationOutcome{
|
||||
StatusCode: resp.StatusCode,
|
||||
Outcome: outcome,
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("DICOM Upload Error (HTTP %d): %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ type SatuSehatClient interface {
|
||||
DoKFA(ctx context.Context, method, endpoint string, body interface{}) ([]byte, error)
|
||||
// DoConsent mengirimkan HTTP request ke endpoint Consent (ConsentURL).
|
||||
DoConsent(ctx context.Context, method, endpoint string, body interface{}) ([]byte, error)
|
||||
// DoKYC mengirimkan HTTP request ke endpoint KYC (KYCURL).
|
||||
DoKYC(ctx context.Context, method, endpoint string, body interface{}) ([]byte, error)
|
||||
// UploadDICOM mengirimkan file biner .dcm menggunakan protokol STOW-RS (multipart/related).
|
||||
UploadDICOM(ctx context.Context, dicomBytes []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// FHIRPayload adalah struktur data fleksibel berbentuk map untuk membangun JSON payload FHIR Satu Sehat.
|
||||
|
||||
Reference in New Issue
Block a user