perbaikan
This commit is contained in:
902
docs/docs.go
902
docs/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,9 @@
|
|||||||
basePath: /api/v1
|
basePath: /api/v1
|
||||||
definitions:
|
definitions:
|
||||||
api-service_internal_models.AggregateData:
|
gin.H:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
models.AggregateData:
|
||||||
properties:
|
properties:
|
||||||
by_dinas:
|
by_dinas:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
@@ -27,7 +30,7 @@ definitions:
|
|||||||
updated_today:
|
updated_today:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
api-service_internal_models.ErrorResponse:
|
models.ErrorResponse:
|
||||||
properties:
|
properties:
|
||||||
code:
|
code:
|
||||||
type: integer
|
type: integer
|
||||||
@@ -38,277 +41,12 @@ definitions:
|
|||||||
timestamp:
|
timestamp:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
api-service_internal_models.MetaResponse:
|
|
||||||
properties:
|
|
||||||
current_page:
|
|
||||||
type: integer
|
|
||||||
has_next:
|
|
||||||
type: boolean
|
|
||||||
has_prev:
|
|
||||||
type: boolean
|
|
||||||
limit:
|
|
||||||
type: integer
|
|
||||||
offset:
|
|
||||||
type: integer
|
|
||||||
total:
|
|
||||||
type: integer
|
|
||||||
total_pages:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
api-service_internal_models.NullableInt32:
|
|
||||||
properties:
|
|
||||||
int32:
|
|
||||||
type: integer
|
|
||||||
valid:
|
|
||||||
type: boolean
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_auth.LoginRequest:
|
|
||||||
properties:
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- password
|
|
||||||
- username
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_auth.TokenResponse:
|
|
||||||
properties:
|
|
||||||
access_token:
|
|
||||||
type: string
|
|
||||||
expires_in:
|
|
||||||
type: integer
|
|
||||||
token_type:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_auth.User:
|
|
||||||
properties:
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
role:
|
|
||||||
type: string
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.Retribusi:
|
|
||||||
properties:
|
|
||||||
date_created:
|
|
||||||
$ref: '#/definitions/sql.NullTime'
|
|
||||||
date_updated:
|
|
||||||
$ref: '#/definitions/sql.NullTime'
|
|
||||||
dinas:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
jenis:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
kelompok_obyek:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
kode_tarif:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
pelayanan:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
rekening_denda:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
rekening_pokok:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
satuan:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
satuan_overtime:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
sort:
|
|
||||||
$ref: '#/definitions/api-service_internal_models.NullableInt32'
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
tarif:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
tarif_overtime:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
uraian_1:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
uraian_2:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
uraian_3:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
user_created:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
user_updated:
|
|
||||||
$ref: '#/definitions/sql.NullString'
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.RetribusiCreateRequest:
|
|
||||||
properties:
|
|
||||||
dinas:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
jenis:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
kelompok_obyek:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
kode_tarif:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
pelayanan:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
rekening_denda:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
rekening_pokok:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
satuan:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
satuan_overtime:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
enum:
|
|
||||||
- draft
|
|
||||||
- active
|
|
||||||
- inactive
|
|
||||||
type: string
|
|
||||||
tarif:
|
|
||||||
type: string
|
|
||||||
tarif_overtime:
|
|
||||||
type: string
|
|
||||||
uraian_1:
|
|
||||||
type: string
|
|
||||||
uraian_2:
|
|
||||||
type: string
|
|
||||||
uraian_3:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.RetribusiCreateResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.Retribusi'
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.RetribusiDeleteResponse:
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.RetribusiGetByIDResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.Retribusi'
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.RetribusiGetResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.Retribusi'
|
|
||||||
type: array
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
meta:
|
|
||||||
$ref: '#/definitions/api-service_internal_models.MetaResponse'
|
|
||||||
summary:
|
|
||||||
$ref: '#/definitions/api-service_internal_models.AggregateData'
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.RetribusiUpdateRequest:
|
|
||||||
properties:
|
|
||||||
dinas:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
jenis:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
kelompok_obyek:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
kode_tarif:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
pelayanan:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
rekening_denda:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
rekening_pokok:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
satuan:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
satuan_overtime:
|
|
||||||
maxLength: 255
|
|
||||||
minLength: 1
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
enum:
|
|
||||||
- draft
|
|
||||||
- active
|
|
||||||
- inactive
|
|
||||||
type: string
|
|
||||||
tarif:
|
|
||||||
type: string
|
|
||||||
tarif_overtime:
|
|
||||||
type: string
|
|
||||||
uraian_1:
|
|
||||||
type: string
|
|
||||||
uraian_2:
|
|
||||||
type: string
|
|
||||||
uraian_3:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
type: object
|
|
||||||
api-service_internal_models_retribusi.RetribusiUpdateResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.Retribusi'
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
gin.H:
|
|
||||||
additionalProperties: {}
|
|
||||||
type: object
|
|
||||||
models.DiagnosaResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
additionalProperties: true
|
|
||||||
type: object
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
models.Flag:
|
models.Flag:
|
||||||
properties:
|
properties:
|
||||||
cob:
|
flag:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- cob
|
- flag
|
||||||
type: object
|
type: object
|
||||||
models.Jaminan:
|
models.Jaminan:
|
||||||
properties:
|
properties:
|
||||||
@@ -345,6 +83,16 @@ definitions:
|
|||||||
penanggungJawab:
|
penanggungJawab:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
models.LoginRequest:
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- password
|
||||||
|
- username
|
||||||
|
type: object
|
||||||
models.LokasiLaka:
|
models.LokasiLaka:
|
||||||
properties:
|
properties:
|
||||||
kdKabupaten:
|
kdKabupaten:
|
||||||
@@ -354,6 +102,30 @@ definitions:
|
|||||||
kdPropinsi:
|
kdPropinsi:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
models.MetaResponse:
|
||||||
|
properties:
|
||||||
|
current_page:
|
||||||
|
type: integer
|
||||||
|
has_next:
|
||||||
|
type: boolean
|
||||||
|
has_prev:
|
||||||
|
type: boolean
|
||||||
|
limit:
|
||||||
|
type: integer
|
||||||
|
offset:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
total_pages:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
models.NullableInt32:
|
||||||
|
properties:
|
||||||
|
int32:
|
||||||
|
type: integer
|
||||||
|
valid:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
models.Penjamin:
|
models.Penjamin:
|
||||||
properties:
|
properties:
|
||||||
keterangan:
|
keterangan:
|
||||||
@@ -371,6 +143,207 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- eksekutif
|
- eksekutif
|
||||||
|
- tujuan
|
||||||
|
type: object
|
||||||
|
models.Retribusi:
|
||||||
|
properties:
|
||||||
|
date_created:
|
||||||
|
$ref: '#/definitions/sql.NullTime'
|
||||||
|
date_updated:
|
||||||
|
$ref: '#/definitions/sql.NullTime'
|
||||||
|
dinas:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
jenis:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
kelompok_obyek:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
kode_tarif:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
pelayanan:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
rekening_denda:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
rekening_pokok:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
satuan:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
satuan_overtime:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
sort:
|
||||||
|
$ref: '#/definitions/models.NullableInt32'
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
tarif:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
tarif_overtime:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
uraian_1:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
uraian_2:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
uraian_3:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
user_created:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
user_updated:
|
||||||
|
$ref: '#/definitions/sql.NullString'
|
||||||
|
type: object
|
||||||
|
models.RetribusiCreateRequest:
|
||||||
|
properties:
|
||||||
|
dinas:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
jenis:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
kelompok_obyek:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
kode_tarif:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
pelayanan:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
rekening_denda:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
rekening_pokok:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
satuan:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
satuan_overtime:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
enum:
|
||||||
|
- draft
|
||||||
|
- active
|
||||||
|
- inactive
|
||||||
|
type: string
|
||||||
|
tarif:
|
||||||
|
type: string
|
||||||
|
tarif_overtime:
|
||||||
|
type: string
|
||||||
|
uraian_1:
|
||||||
|
type: string
|
||||||
|
uraian_2:
|
||||||
|
type: string
|
||||||
|
uraian_3:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
models.RetribusiCreateResponse:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/models.Retribusi'
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
models.RetribusiDeleteResponse:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
models.RetribusiGetByIDResponse:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/models.Retribusi'
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
models.RetribusiGetResponse:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.Retribusi'
|
||||||
|
type: array
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
meta:
|
||||||
|
$ref: '#/definitions/models.MetaResponse'
|
||||||
|
summary:
|
||||||
|
$ref: '#/definitions/models.AggregateData'
|
||||||
|
type: object
|
||||||
|
models.RetribusiUpdateRequest:
|
||||||
|
properties:
|
||||||
|
dinas:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
jenis:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
kelompok_obyek:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
kode_tarif:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
pelayanan:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
rekening_denda:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
rekening_pokok:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
satuan:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
satuan_overtime:
|
||||||
|
maxLength: 255
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
enum:
|
||||||
|
- draft
|
||||||
|
- active
|
||||||
|
- inactive
|
||||||
|
type: string
|
||||||
|
tarif:
|
||||||
|
type: string
|
||||||
|
tarif_overtime:
|
||||||
|
type: string
|
||||||
|
uraian_1:
|
||||||
|
type: string
|
||||||
|
uraian_2:
|
||||||
|
type: string
|
||||||
|
uraian_3:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
models.RetribusiUpdateResponse:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/models.Retribusi'
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
models.Rujukan:
|
models.Rujukan:
|
||||||
properties:
|
properties:
|
||||||
@@ -390,17 +363,17 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
models.SepPostRequest:
|
models.SepPostRequest:
|
||||||
properties:
|
properties:
|
||||||
t_sep:
|
tsep:
|
||||||
$ref: '#/definitions/models.TSepPost'
|
$ref: '#/definitions/models.TSepPost'
|
||||||
required:
|
required:
|
||||||
- t_sep
|
- tsep
|
||||||
type: object
|
type: object
|
||||||
models.SepPutRequest:
|
models.SepPutRequest:
|
||||||
properties:
|
properties:
|
||||||
t_sep:
|
tsep:
|
||||||
$ref: '#/definitions/models.TSepPut'
|
$ref: '#/definitions/models.TSepPut'
|
||||||
required:
|
required:
|
||||||
- t_sep
|
- tsep
|
||||||
type: object
|
type: object
|
||||||
models.SepResponse:
|
models.SepResponse:
|
||||||
properties:
|
properties:
|
||||||
@@ -520,6 +493,26 @@ definitions:
|
|||||||
- noSep
|
- noSep
|
||||||
- user
|
- user
|
||||||
type: object
|
type: object
|
||||||
|
models.TokenResponse:
|
||||||
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
token_type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
models.User:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
sql.NullString:
|
sql.NullString:
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
@@ -561,14 +554,14 @@ paths:
|
|||||||
name: login
|
name: login
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_auth.LoginRequest'
|
$ref: '#/definitions/models.LoginRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_auth.TokenResponse'
|
$ref: '#/definitions/models.TokenResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request
|
description: Bad request
|
||||||
schema:
|
schema:
|
||||||
@@ -593,7 +586,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_auth.User'
|
$ref: '#/definitions/models.User'
|
||||||
"401":
|
"401":
|
||||||
description: Unauthorized
|
description: Unauthorized
|
||||||
schema:
|
schema:
|
||||||
@@ -625,7 +618,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_auth.TokenResponse'
|
$ref: '#/definitions/models.TokenResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request
|
description: Bad request
|
||||||
schema:
|
schema:
|
||||||
@@ -715,36 +708,6 @@ paths:
|
|||||||
summary: Get participant data by NIK
|
summary: Get participant data by NIK
|
||||||
tags:
|
tags:
|
||||||
- bpjs
|
- bpjs
|
||||||
/api/v1/bpjs/reference/referensi/diagnosa:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Get all diagnosa reference data
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Success response
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/models.DiagnosaResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad request
|
|
||||||
schema:
|
|
||||||
additionalProperties: true
|
|
||||||
type: object
|
|
||||||
"404":
|
|
||||||
description: Data not found
|
|
||||||
schema:
|
|
||||||
additionalProperties: true
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal server error
|
|
||||||
schema:
|
|
||||||
additionalProperties: true
|
|
||||||
type: object
|
|
||||||
summary: Get all diagnosa reference data
|
|
||||||
tags:
|
|
||||||
- bpjs/reference
|
|
||||||
/api/v1/retribusi/{id}:
|
/api/v1/retribusi/{id}:
|
||||||
delete:
|
delete:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -762,19 +725,19 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Retribusi deleted successfully
|
description: Retribusi deleted successfully
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse'
|
$ref: '#/definitions/models.RetribusiDeleteResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Invalid ID format
|
description: Invalid ID format
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"404":
|
"404":
|
||||||
description: Retribusi not found
|
description: Retribusi not found
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
summary: Delete retribusi
|
summary: Delete retribusi
|
||||||
tags:
|
tags:
|
||||||
- retribusi
|
- retribusi
|
||||||
@@ -794,19 +757,19 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Success response
|
description: Success response
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse'
|
$ref: '#/definitions/models.RetribusiGetByIDResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Invalid ID format
|
description: Invalid ID format
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"404":
|
"404":
|
||||||
description: Retribusi not found
|
description: Retribusi not found
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
summary: Get Retribusi by ID
|
summary: Get Retribusi by ID
|
||||||
tags:
|
tags:
|
||||||
- retribusi
|
- retribusi
|
||||||
@@ -825,26 +788,26 @@ paths:
|
|||||||
name: request
|
name: request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest'
|
$ref: '#/definitions/models.RetribusiUpdateRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Retribusi updated successfully
|
description: Retribusi updated successfully
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse'
|
$ref: '#/definitions/models.RetribusiUpdateResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request or validation error
|
description: Bad request or validation error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"404":
|
"404":
|
||||||
description: Retribusi not found
|
description: Retribusi not found
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
summary: Update retribusi
|
summary: Update retribusi
|
||||||
tags:
|
tags:
|
||||||
- retribusi
|
- retribusi
|
||||||
@@ -891,15 +854,15 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Success response
|
description: Success response
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse'
|
$ref: '#/definitions/models.RetribusiGetResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request
|
description: Bad request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
summary: Get retribusi with pagination and optional aggregation
|
summary: Get retribusi with pagination and optional aggregation
|
||||||
tags:
|
tags:
|
||||||
- retribusi
|
- retribusi
|
||||||
@@ -913,22 +876,22 @@ paths:
|
|||||||
name: request
|
name: request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest'
|
$ref: '#/definitions/models.RetribusiCreateRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: Retribusi created successfully
|
description: Retribusi created successfully
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse'
|
$ref: '#/definitions/models.RetribusiCreateResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request or validation error
|
description: Bad request or validation error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
summary: Create retribusi
|
summary: Create retribusi
|
||||||
tags:
|
tags:
|
||||||
- retribusi
|
- retribusi
|
||||||
@@ -966,15 +929,15 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Success response
|
description: Success response
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse'
|
$ref: '#/definitions/models.RetribusiGetResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request
|
description: Bad request
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
"500":
|
"500":
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
summary: Get retribusi with dynamic filtering
|
summary: Get retribusi with dynamic filtering
|
||||||
tags:
|
tags:
|
||||||
- retribusi
|
- retribusi
|
||||||
@@ -994,11 +957,11 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: Statistics data
|
description: Statistics data
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.AggregateData'
|
$ref: '#/definitions/models.AggregateData'
|
||||||
"500":
|
"500":
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models.ErrorResponse'
|
$ref: '#/definitions/models.ErrorResponse'
|
||||||
summary: Get retribusi statistics
|
summary: Get retribusi statistics
|
||||||
tags:
|
tags:
|
||||||
- retribusi
|
- retribusi
|
||||||
@@ -1013,14 +976,14 @@ paths:
|
|||||||
name: token
|
name: token
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_auth.LoginRequest'
|
$ref: '#/definitions/models.LoginRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_auth.TokenResponse'
|
$ref: '#/definitions/models.TokenResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request
|
description: Bad request
|
||||||
schema:
|
schema:
|
||||||
@@ -1057,7 +1020,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/api-service_internal_models_auth.TokenResponse'
|
$ref: '#/definitions/models.TokenResponse'
|
||||||
"400":
|
"400":
|
||||||
description: Bad request
|
description: Bad request
|
||||||
schema:
|
schema:
|
||||||
@@ -1096,11 +1059,11 @@ paths:
|
|||||||
$ref: '#/definitions/gin.H'
|
$ref: '#/definitions/gin.H'
|
||||||
summary: Create a new SEP
|
summary: Create a new SEP
|
||||||
tags:
|
tags:
|
||||||
- bpjs
|
- SEP
|
||||||
put:
|
put:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Update an existing Surat Eligibilitas Peserta
|
description: Update Surat Eligibilitas Peserta
|
||||||
parameters:
|
parameters:
|
||||||
- description: SEP update request
|
- description: SEP update request
|
||||||
in: body
|
in: body
|
||||||
@@ -1123,9 +1086,9 @@ paths:
|
|||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/gin.H'
|
$ref: '#/definitions/gin.H'
|
||||||
summary: Update an existing SEP
|
summary: Update SEP
|
||||||
tags:
|
tags:
|
||||||
- bpjs
|
- SEP
|
||||||
/sep/{noSep}:
|
/sep/{noSep}:
|
||||||
delete:
|
delete:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -1157,9 +1120,9 @@ paths:
|
|||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/gin.H'
|
$ref: '#/definitions/gin.H'
|
||||||
summary: Delete an existing SEP
|
summary: Delete SEP
|
||||||
tags:
|
tags:
|
||||||
- bpjs
|
- SEP
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
@@ -1185,9 +1148,9 @@ paths:
|
|||||||
description: Internal server error
|
description: Internal server error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/gin.H'
|
$ref: '#/definitions/gin.H'
|
||||||
summary: Get an existing SEP
|
summary: Get SEP
|
||||||
tags:
|
tags:
|
||||||
- bpjs
|
- SEP
|
||||||
schemes:
|
schemes:
|
||||||
- http
|
- http
|
||||||
- https
|
- https
|
||||||
|
|||||||
14
example.env
14
example.env
@@ -77,4 +77,16 @@ BRIDGING_SATUSEHAT_CLIENT_SECRET=Al3PTYAW6axPiAFwaFlpn8qShLFW5YGMgG8w1qhexgCc7lG
|
|||||||
BRIDGING_SATUSEHAT_AUTH_URL=https://api-satusehat.kemkes.go.id/oauth2/v1
|
BRIDGING_SATUSEHAT_AUTH_URL=https://api-satusehat.kemkes.go.id/oauth2/v1
|
||||||
BRIDGING_SATUSEHAT_BASE_URL=https://api-satusehat.kemkes.go.id/fhir-r4/v1
|
BRIDGING_SATUSEHAT_BASE_URL=https://api-satusehat.kemkes.go.id/fhir-r4/v1
|
||||||
BRIDGING_SATUSEHAT_CONSENT_URL=https://api-satusehat.dto.kemkes.go.id/consent/v1
|
BRIDGING_SATUSEHAT_CONSENT_URL=https://api-satusehat.dto.kemkes.go.id/consent/v1
|
||||||
BRIDGING_SATUSEHAT_KFA_URL=https://api-satusehat.kemkes.go.id/kfa-v2
|
BRIDGING_SATUSEHAT_KFA_URL=https://api-satusehat.kemkes.go.id/kfa-v2
|
||||||
|
|
||||||
|
SWAGGER_TITLE=My Custom API Service
|
||||||
|
SWAGGER_DESCRIPTION=This is a custom API service for managing various resources
|
||||||
|
SWAGGER_VERSION=2.0.0
|
||||||
|
SWAGGER_CONTACT_NAME=STIM IT Support
|
||||||
|
SWAGGER_HOST=api.mycompany.com:8080
|
||||||
|
SWAGGER_BASE_PATH=/api/v2
|
||||||
|
SWAGGER_SCHEMES=https
|
||||||
|
|
||||||
|
API_TITLE=API Service UJICOBA
|
||||||
|
API_DESCRIPTION=Dokumentation SWAGGER
|
||||||
|
API_VERSION=3.0.0
|
||||||
|
|||||||
175
examples/satusehat_specific_requests.go
Normal file
175
examples/satusehat_specific_requests.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"api-service/internal/config"
|
||||||
|
services "api-service/internal/services/satusehat"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("SatuSehat Specific Requests - Organization & Patient by NIK")
|
||||||
|
fmt.Println("==========================================================")
|
||||||
|
|
||||||
|
// Load environment variables from .env file
|
||||||
|
err := godotenv.Load("../.env")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Could not load .env file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set debug logging
|
||||||
|
os.Setenv("LOG_LEVEL", "debug")
|
||||||
|
|
||||||
|
// Load configuration from environment variables
|
||||||
|
cfg := config.LoadConfig()
|
||||||
|
|
||||||
|
// Create SatuSehat service
|
||||||
|
service := services.NewSatuSehatServiceFromConfig(cfg)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Test 1: Get Organization by ID (using OrgID from config)
|
||||||
|
fmt.Println("\n1. Get Organization by ID:")
|
||||||
|
orgID := cfg.SatuSehat.OrgID
|
||||||
|
fmt.Printf(" Using OrgID: %s\n", orgID)
|
||||||
|
|
||||||
|
orgEndpoint := fmt.Sprintf("/Organization/%s", orgID)
|
||||||
|
orgResult, err := service.GetRawResponse(ctx, orgEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting organization: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Status Code: %d\n", orgResult.StatusCode)
|
||||||
|
fmt.Printf(" Success: %v\n", orgResult.Success)
|
||||||
|
fmt.Printf(" Message: %s\n", orgResult.Message)
|
||||||
|
|
||||||
|
if orgResult.Data != nil {
|
||||||
|
fmt.Printf(" Data Type: %T\n", orgResult.Data)
|
||||||
|
|
||||||
|
// Convert data to JSON for better readability
|
||||||
|
if jsonData, err := json.MarshalIndent(orgResult.Data, " ", " "); err == nil {
|
||||||
|
fmt.Printf(" Organization Data: \n%s\n", string(jsonData))
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Data (raw): %+v\n", orgResult.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgResult.Error != nil {
|
||||||
|
fmt.Printf(" Error Code: %s\n", orgResult.Error.Code)
|
||||||
|
fmt.Printf(" Error Details: %s\n", orgResult.Error.Details)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Get Patient by specific NIK
|
||||||
|
fmt.Println("\n2. Get Patient by Specific NIK:")
|
||||||
|
specificNIK := "3512162601960002"
|
||||||
|
fmt.Printf(" Using NIK: %s\n", specificNIK)
|
||||||
|
|
||||||
|
patientResult, err := service.GetPatientByNIK(ctx, specificNIK)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting patient: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Status Code: %d\n", patientResult.StatusCode)
|
||||||
|
fmt.Printf(" Success: %v\n", patientResult.Success)
|
||||||
|
fmt.Printf(" Message: %s\n", patientResult.Message)
|
||||||
|
|
||||||
|
if patientResult.Data != nil {
|
||||||
|
fmt.Printf(" Data Type: %T\n", patientResult.Data)
|
||||||
|
|
||||||
|
// Convert data to JSON for better readability
|
||||||
|
if jsonData, err := json.MarshalIndent(patientResult.Data, " ", " "); err == nil {
|
||||||
|
fmt.Printf(" Patient Data: \n%s\n", string(jsonData))
|
||||||
|
|
||||||
|
// Extract and display specific patient information
|
||||||
|
displayPatientDetails(patientResult.Data)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Data (raw): %+v\n", patientResult.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if patientResult.Error != nil {
|
||||||
|
fmt.Printf(" Error Code: %s\n", patientResult.Error.Code)
|
||||||
|
fmt.Printf(" Error Details: %s\n", patientResult.Error.Details)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Health check to verify token status
|
||||||
|
fmt.Println("\n3. Service Health Check:")
|
||||||
|
isValid := service.IsTokenValid()
|
||||||
|
fmt.Printf(" Token Valid: %v\n", isValid)
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
fmt.Println(" Refreshing token...")
|
||||||
|
err = service.RefreshToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error refreshing token: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println(" Token refreshed successfully")
|
||||||
|
fmt.Printf(" Token Valid After Refresh: %v\n", service.IsTokenValid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\nSpecific requests test completed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// displayPatientDetails extracts and displays specific patient information from FHIR response
|
||||||
|
func displayPatientDetails(data interface{}) {
|
||||||
|
fmt.Println("\n --- Patient Details ---")
|
||||||
|
|
||||||
|
// Convert to map for easier access
|
||||||
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
||||||
|
// Check if it's a Bundle
|
||||||
|
if resourceType, exists := dataMap["resourceType"]; exists && resourceType == "Bundle" {
|
||||||
|
if entries, exists := dataMap["entry"]; exists {
|
||||||
|
if entryList, ok := entries.([]interface{}); ok && len(entryList) > 0 {
|
||||||
|
if firstEntry, ok := entryList[0].(map[string]interface{}); ok {
|
||||||
|
if resource, exists := firstEntry["resource"]; exists {
|
||||||
|
if patient, ok := resource.(map[string]interface{}); ok {
|
||||||
|
// Display basic patient info
|
||||||
|
fmt.Printf(" Patient ID: %v\n", patient["id"])
|
||||||
|
|
||||||
|
// Display name
|
||||||
|
if names, exists := patient["name"].([]interface{}); exists && len(names) > 0 {
|
||||||
|
if name, ok := names[0].(map[string]interface{}); ok {
|
||||||
|
fmt.Printf(" Name: %v\n", name["text"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display identifiers
|
||||||
|
if identifiers, exists := patient["identifier"].([]interface{}); exists {
|
||||||
|
fmt.Println(" Identifiers:")
|
||||||
|
for _, ident := range identifiers {
|
||||||
|
if identifier, ok := ident.(map[string]interface{}); ok {
|
||||||
|
system := identifier["system"]
|
||||||
|
value := identifier["value"]
|
||||||
|
fmt.Printf(" - %s: %v\n", system, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display status
|
||||||
|
fmt.Printf(" Active: %v\n", patient["active"])
|
||||||
|
|
||||||
|
// Display meta information
|
||||||
|
if meta, exists := patient["meta"].(map[string]interface{}); exists {
|
||||||
|
fmt.Printf(" Last Updated: %v\n", meta["lastUpdated"])
|
||||||
|
if profiles, exists := meta["profile"].([]interface{}); exists {
|
||||||
|
fmt.Printf(" FHIR Profile: %v\n", profiles[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(" -----------------------")
|
||||||
|
}
|
||||||
7
go.mod
7
go.mod
@@ -21,9 +21,10 @@ require (
|
|||||||
github.com/go-sql-driver/mysql v1.8.1
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mashingan/smapping v0.1.19
|
github.com/mashingan/smapping v0.1.19
|
||||||
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/swaggo/swag v1.16.6
|
github.com/tidwall/gjson v1.18.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -57,6 +58,7 @@ require (
|
|||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.6 // indirect
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/microsoft/go-mssqldb v1.8.2 // indirect
|
github.com/microsoft/go-mssqldb v1.8.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
@@ -64,6 +66,9 @@ require (
|
|||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
|
github.com/swaggo/swag v1.16.6 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
|||||||
17
go.sum
17
go.sum
@@ -30,6 +30,7 @@ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZw
|
|||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/daku10/go-lz-string v0.0.6 h1:aO8FFp4QPuNp7+WNyh1DyNjGF3UbZu95tUv9xOZNsYQ=
|
github.com/daku10/go-lz-string v0.0.6 h1:aO8FFp4QPuNp7+WNyh1DyNjGF3UbZu95tUv9xOZNsYQ=
|
||||||
github.com/daku10/go-lz-string v0.0.6/go.mod h1:Vk++rSG3db8HXJaHEAbxiy/ukjTmPBw/iI+SrVZDzfs=
|
github.com/daku10/go-lz-string v0.0.6/go.mod h1:Vk++rSG3db8HXJaHEAbxiy/ukjTmPBw/iI+SrVZDzfs=
|
||||||
@@ -68,6 +69,7 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
|||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
@@ -136,6 +138,10 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
|
|||||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mashingan/smapping v0.1.19 h1:SsEtuPn2UcM1croIupPtGLgWgpYRuS0rSQMvKD9g2BQ=
|
github.com/mashingan/smapping v0.1.19 h1:SsEtuPn2UcM1croIupPtGLgWgpYRuS0rSQMvKD9g2BQ=
|
||||||
github.com/mashingan/smapping v0.1.19/go.mod h1:FjfiwFxGOuNxL/OT1WcrNAwTPx0YJeg5JiXwBB1nyig=
|
github.com/mashingan/smapping v0.1.19/go.mod h1:FjfiwFxGOuNxL/OT1WcrNAwTPx0YJeg5JiXwBB1nyig=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=
|
github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=
|
||||||
@@ -156,12 +162,16 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzL
|
|||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -183,6 +193,12 @@ github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+z
|
|||||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
@@ -258,6 +274,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
@@ -20,6 +20,22 @@ type Config struct {
|
|||||||
Keycloak KeycloakConfig
|
Keycloak KeycloakConfig
|
||||||
Bpjs BpjsConfig
|
Bpjs BpjsConfig
|
||||||
SatuSehat SatuSehatConfig
|
SatuSehat SatuSehatConfig
|
||||||
|
Swagger SwaggerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwaggerConfig struct {
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Version string
|
||||||
|
TermsOfService string
|
||||||
|
ContactName string
|
||||||
|
ContactURL string
|
||||||
|
ContactEmail string
|
||||||
|
LicenseName string
|
||||||
|
LicenseURL string
|
||||||
|
Host string
|
||||||
|
BasePath string
|
||||||
|
Schemes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
@@ -141,6 +157,20 @@ func LoadConfig() *Config {
|
|||||||
KFAURL: getEnv("BRIDGING_SATUSEHAT_KFA_URL", "https://api-satusehat.kemkes.go.id/kfa-v2"),
|
KFAURL: getEnv("BRIDGING_SATUSEHAT_KFA_URL", "https://api-satusehat.kemkes.go.id/kfa-v2"),
|
||||||
Timeout: parseDuration(getEnv("BRIDGING_SATUSEHAT_TIMEOUT", "30s")),
|
Timeout: parseDuration(getEnv("BRIDGING_SATUSEHAT_TIMEOUT", "30s")),
|
||||||
},
|
},
|
||||||
|
Swagger: SwaggerConfig{
|
||||||
|
Title: getEnv("SWAGGER_TITLE", "SERVICE API"),
|
||||||
|
Description: getEnv("SWAGGER_DESCRIPTION", "CUSTUM SERVICE API"),
|
||||||
|
Version: getEnv("SWAGGER_VERSION", "1.0.0"),
|
||||||
|
TermsOfService: getEnv("SWAGGER_TERMS_OF_SERVICE", "http://swagger.io/terms/"),
|
||||||
|
ContactName: getEnv("SWAGGER_CONTACT_NAME", "API Support"),
|
||||||
|
ContactURL: getEnv("SWAGGER_CONTACT_URL", "http://rssa.example.com/support"),
|
||||||
|
ContactEmail: getEnv("SWAGGER_CONTACT_EMAIL", "support@swagger.io"),
|
||||||
|
LicenseName: getEnv("SWAGGER_LICENSE_NAME", "Apache 2.0"),
|
||||||
|
LicenseURL: getEnv("SWAGGER_LICENSE_URL", "http://www.apache.org/licenses/LICENSE-2.0.html"),
|
||||||
|
Host: getEnv("SWAGGER_HOST", "localhost:8080"),
|
||||||
|
BasePath: getEnv("SWAGGER_BASE_PATH", "/api/v1"),
|
||||||
|
Schemes: parseSchemes(getEnv("SWAGGER_SCHEMES", "http,https")),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load database configurations
|
// Load database configurations
|
||||||
@@ -600,6 +630,19 @@ func getEnvAsBool(key string, defaultValue bool) bool {
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseSchemes parses comma-separated schemes string into a slice
|
||||||
|
func parseSchemes(schemesStr string) []string {
|
||||||
|
if schemesStr == "" {
|
||||||
|
return []string{"http"}
|
||||||
|
}
|
||||||
|
|
||||||
|
schemes := strings.Split(schemesStr, ",")
|
||||||
|
for i, scheme := range schemes {
|
||||||
|
schemes[i] = strings.TrimSpace(scheme)
|
||||||
|
}
|
||||||
|
return schemes
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
if len(c.Databases) == 0 {
|
if len(c.Databases) == 0 {
|
||||||
log.Fatal("At least one database configuration is required")
|
log.Fatal("At least one database configuration is required")
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
"api-service/internal/config"
|
|
||||||
services "api-service/internal/services/bpjs"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DiagnosaHandler handles BPJS diagnosa operations
|
|
||||||
type DiagnosaHandler struct {
|
|
||||||
bpjsService services.VClaimService
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDiagnosaHandler creates a new DiagnosaHandler instance
|
|
||||||
func NewDiagnosaHandler(cfg config.BpjsConfig) *DiagnosaHandler {
|
|
||||||
return &DiagnosaHandler{
|
|
||||||
bpjsService: services.NewService(cfg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll godoc
|
|
||||||
// @Summary Get all diagnosa reference data
|
|
||||||
// @Description Get all diagnosa reference data
|
|
||||||
// @Tags bpjs/reference
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} models.DiagnosaResponse "Success response"
|
|
||||||
// @Failure 400 {object} map[string]interface{} "Bad request"
|
|
||||||
// @Failure 404 {object} map[string]interface{} "Data not found"
|
|
||||||
// @Failure 500 {object} map[string]interface{} "Internal server error"
|
|
||||||
// @Router /api/v1/bpjs/reference/referensi/diagnosa [get]
|
|
||||||
func (h *DiagnosaHandler) GetAll(c *gin.Context) {
|
|
||||||
|
|
||||||
// Create context with timeout
|
|
||||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Build endpoint URL
|
|
||||||
endpoint := "/referensi/diagnosa"
|
|
||||||
|
|
||||||
// Call BPJS service
|
|
||||||
var result map[string]interface{}
|
|
||||||
if err := h.bpjsService.Get(ctx, endpoint, &result); err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": "Failed to fetch diagnosa data",
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return successful response
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "Data diagnosa berhasil diambil",
|
|
||||||
"data": result,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Helper methods for error handling and response formatting
|
|
||||||
|
|
||||||
// handleBPJSError handles BPJS service errors and returns appropriate HTTP responses
|
|
||||||
func (h *DiagnosaHandler) handleBPJSError(c *gin.Context, err error, operation string) {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": fmt.Sprintf("Failed to %s", operation),
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateDateFormat validates if the date string is in yyyy-MM-dd format
|
|
||||||
func (h *DiagnosaHandler) validateDateFormat(dateStr string) error {
|
|
||||||
_, err := time.Parse("2006-01-02", dateStr)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildSuccessResponse builds a standardized success response
|
|
||||||
func (h *DiagnosaHandler) buildSuccessResponse(message string, data interface{}) gin.H {
|
|
||||||
return gin.H{
|
|
||||||
"message": message,
|
|
||||||
"data": data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
24
internal/handlers/healthcheck/healthcheck.go
Normal file
24
internal/handlers/healthcheck/healthcheck.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"api-service/internal/database"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HealthCheckHandler handles health check requests
|
||||||
|
type HealthCheckHandler struct {
|
||||||
|
dbService database.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHealthCheckHandler creates a new HealthCheckHandler
|
||||||
|
func NewHealthCheckHandler(dbService database.Service) *HealthCheckHandler {
|
||||||
|
return &HealthCheckHandler{dbService: dbService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckHealth checks the health of the application
|
||||||
|
func (h *HealthCheckHandler) CheckHealth(c *gin.Context) {
|
||||||
|
healthStatus := h.dbService.Health() // Call the health check function from the database service
|
||||||
|
c.JSON(http.StatusOK, healthStatus)
|
||||||
|
}
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
100
internal/handlers/swagger/swagger.go
Normal file
100
internal/handlers/swagger/swagger.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package swagger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
swaggerFiles "github.com/swaggo/files"
|
||||||
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
|
||||||
|
"api-service/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler handles Swagger documentation
|
||||||
|
type Handler struct {
|
||||||
|
config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates a new Swagger handler
|
||||||
|
func NewHandler(cfg *config.Config) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterRoutes registers Swagger routes
|
||||||
|
func (h *Handler) RegisterRoutes(router *gin.Engine) {
|
||||||
|
// Serve Swagger UI
|
||||||
|
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
|
||||||
|
// Serve OpenAPI spec
|
||||||
|
router.GET("/openapi.json", h.serveOpenAPISpec)
|
||||||
|
router.GET("/openapi.yaml", h.serveOpenAPISpecYAML)
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveOpenAPISpec serves the OpenAPI JSON specification
|
||||||
|
func (h *Handler) serveOpenAPISpec(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": map[string]interface{}{
|
||||||
|
"title": h.config.Swagger.Title,
|
||||||
|
"description": h.config.Swagger.Description,
|
||||||
|
"version": h.config.Swagger.Version,
|
||||||
|
"termsOfService": h.config.Swagger.TermsOfService,
|
||||||
|
"contact": map[string]interface{}{
|
||||||
|
"name": h.config.Swagger.ContactName,
|
||||||
|
"url": h.config.Swagger.ContactURL,
|
||||||
|
"email": h.config.Swagger.ContactEmail,
|
||||||
|
},
|
||||||
|
"license": map[string]interface{}{
|
||||||
|
"name": h.config.Swagger.LicenseName,
|
||||||
|
"url": h.config.Swagger.LicenseURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"servers": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"url": strings.Join([]string{strings.ToLower(h.config.Swagger.Schemes[0]), "://", h.config.Swagger.Host, h.config.Swagger.BasePath}, ""),
|
||||||
|
"description": "API Server",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"paths": map[string]interface{}{},
|
||||||
|
"components": map[string]interface{}{
|
||||||
|
"schemas": map[string]interface{}{},
|
||||||
|
"securitySchemes": map[string]interface{}{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveOpenAPISpecYAML serves the OpenAPI YAML specification
|
||||||
|
func (h *Handler) serveOpenAPISpecYAML(c *gin.Context) {
|
||||||
|
c.YAML(http.StatusOK, map[string]interface{}{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": map[string]interface{}{
|
||||||
|
"title": h.config.Swagger.Title,
|
||||||
|
"description": h.config.Swagger.Description,
|
||||||
|
"version": h.config.Swagger.Version,
|
||||||
|
"termsOfService": h.config.Swagger.TermsOfService,
|
||||||
|
"contact": map[string]interface{}{
|
||||||
|
"name": h.config.Swagger.ContactName,
|
||||||
|
"url": h.config.Swagger.ContactURL,
|
||||||
|
"email": h.config.Swagger.ContactEmail,
|
||||||
|
},
|
||||||
|
"license": map[string]interface{}{
|
||||||
|
"name": h.config.Swagger.LicenseName,
|
||||||
|
"url": h.config.Swagger.LicenseURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"servers": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"url": strings.Join([]string{strings.ToLower(h.config.Swagger.Schemes[0]), "://", h.config.Swagger.Host, h.config.Swagger.BasePath}, ""),
|
||||||
|
"description": "API Server",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"paths": map[string]interface{}{},
|
||||||
|
"components": map[string]interface{}{
|
||||||
|
"schemas": map[string]interface{}{},
|
||||||
|
"securitySchemes": map[string]interface{}{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -7,8 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"api-service/internal/config"
|
"api-service/internal/config"
|
||||||
models "api-service/internal/models/bpjs/vclaim"
|
vclaimModels "api-service/internal/models/vclaim"
|
||||||
|
|
||||||
services "api-service/internal/services/bpjs"
|
services "api-service/internal/services/bpjs"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -27,18 +26,22 @@ func NewSepHandler(cfg config.BpjsConfig) *SepHandler {
|
|||||||
// CreateSEP godoc
|
// CreateSEP godoc
|
||||||
// @Summary Create a new SEP
|
// @Summary Create a new SEP
|
||||||
// @Description Create a new Surat Eligibilitas Peserta
|
// @Description Create a new Surat Eligibilitas Peserta
|
||||||
// @Tags bpjs
|
// @Tags SEP
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param request body models.SepPostRequest true "SEP creation request"
|
// @Param request body vclaimModels.SepPostRequest true "SEP creation request"
|
||||||
// @Success 200 {object} models.SepResponse "SEP created successfully"
|
// @Success 200 {object} vclaimModels.SepResponse "SEP created successfully"
|
||||||
// @Failure 400 {object} gin.H "Invalid request"
|
// @Failure 400 {object} gin.H "Invalid request"
|
||||||
// @Failure 500 {object} gin.H "Internal server error"
|
// @Failure 500 {object} gin.H "Internal server error"
|
||||||
// @Router /sep [post]
|
// @Router /sep [post]
|
||||||
func (h *SepHandler) CreateSEP(c *gin.Context) {
|
func (h *SepHandler) CreateSEP(c *gin.Context) {
|
||||||
var req models.SepPostRequest
|
var req vclaimModels.SepPostRequest
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid body",
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,32 +49,39 @@ func (h *SepHandler) CreateSEP(c *gin.Context) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
if err := h.service.Post(ctx, "/SEP/2.0/insert", req, &result); err != nil {
|
if err := h.service.Post(ctx, "SEP/2.0/insert", req, &result); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "create failed", "message": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "create failed",
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models.SepResponse{
|
c.JSON(http.StatusOK, vclaimModels.SepResponse{
|
||||||
Message: "SEP berhasil dibuat",
|
Message: "SEP berhasil dibuat",
|
||||||
Data: result,
|
Data: result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSEP godoc
|
// UpdateSEP godoc
|
||||||
// @Summary Update an existing SEP
|
// @Summary Update SEP
|
||||||
// @Description Update an existing Surat Eligibilitas Peserta
|
// @Description Update Surat Eligibilitas Peserta
|
||||||
// @Tags bpjs
|
// @Tags SEP
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param request body models.SepPutRequest true "SEP update request"
|
// @Param request body vclaimModels.SepPutRequest true "SEP update request"
|
||||||
// @Success 200 {object} models.SepResponse "SEP updated successfully"
|
// @Success 200 {object} vclaimModels.SepResponse "SEP updated successfully"
|
||||||
// @Failure 400 {object} gin.H "Invalid request"
|
// @Failure 400 {object} gin.H "Invalid request"
|
||||||
// @Failure 500 {object} gin.H "Internal server error"
|
// @Failure 500 {object} gin.H "Internal server error"
|
||||||
// @Router /sep [put]
|
// @Router /sep [put]
|
||||||
func (h *SepHandler) UpdateSEP(c *gin.Context) {
|
func (h *SepHandler) UpdateSEP(c *gin.Context) {
|
||||||
var req models.SepPutRequest
|
var req vclaimModels.SepPutRequest
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid body",
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,85 +89,101 @@ func (h *SepHandler) UpdateSEP(c *gin.Context) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
if err := h.service.Put(ctx, "/SEP/2.0/update", req, &result); err != nil {
|
if err := h.service.Put(ctx, "SEP/2.0/update", req, &result); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed", "message": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "update failed",
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models.SepResponse{
|
c.JSON(http.StatusOK, vclaimModels.SepResponse{
|
||||||
Message: "SEP berhasil diperbarui",
|
Message: "SEP berhasil diperbarui",
|
||||||
Data: result,
|
Data: result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSEP godoc
|
// DeleteSEP godoc
|
||||||
// @Summary Delete an existing SEP
|
// @Summary Delete SEP
|
||||||
// @Description Delete a Surat Eligibilitas Peserta by noSep
|
// @Description Delete a Surat Eligibilitas Peserta by noSep
|
||||||
// @Tags bpjs
|
// @Tags SEP
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param noSep path string true "No SEP"
|
// @Param noSep path string true "No SEP"
|
||||||
// @Param user query string true "User"
|
// @Param user query string true "User"
|
||||||
// @Success 200 {object} models.SepResponse "SEP deleted successfully"
|
// @Success 200 {object} vclaimModels.SepResponse "SEP deleted successfully"
|
||||||
// @Failure 400 {object} gin.H "Invalid request"
|
// @Failure 400 {object} gin.H "Invalid request"
|
||||||
// @Failure 500 {object} gin.H "Internal server error"
|
// @Failure 500 {object} gin.H "Internal server error"
|
||||||
// @Router /sep/{noSep} [delete]
|
// @Router /sep/{noSep} [delete]
|
||||||
func (h *SepHandler) DeleteSEP(c *gin.Context) {
|
func (h *SepHandler) DeleteSEP(c *gin.Context) {
|
||||||
noSep := c.Param("noSep")
|
noSep := c.Param("noSep")
|
||||||
user := c.Query("user")
|
user := c.Query("user")
|
||||||
|
|
||||||
if noSep == "" || user == "" {
|
if noSep == "" || user == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "noSep & user required"})
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "noSep and user required",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body := models.SepDeleteRequest{}
|
body := vclaimModels.SepDeleteRequest{}
|
||||||
body.TSep.NoSep = noSep
|
body.TSep.NoSep = noSep
|
||||||
body.TSep.User = user
|
body.TSep.User = user
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(c, 30*time.Second)
|
ctx, cancel := context.WithTimeout(c, 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := h.service.Delete(ctx, "/SEP/2.0/delete", body); err != nil {
|
if err := h.service.Delete(ctx, "SEP/2.0/delete", body); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed", "message": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "delete failed",
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
c.JSON(http.StatusOK, models.SepResponse{
|
c.JSON(http.StatusOK, vclaimModels.SepResponse{
|
||||||
Message: "SEP berhasil dihapus",
|
Message: "SEP berhasil dihapus",
|
||||||
Data: result,
|
Data: result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSEP godoc
|
// GetSEP godoc
|
||||||
// @Summary Get an existing SEP
|
// @Summary Get SEP
|
||||||
// @Description Retrieve a Surat Eligibilitas Peserta by noSep
|
// @Description Retrieve a Surat Eligibilitas Peserta by noSep
|
||||||
// @Tags bpjs
|
// @Tags SEP
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param noSep path string true "No SEP"
|
// @Param noSep path string true "No SEP"
|
||||||
// @Success 200 {object} models.SepResponse "Data SEP retrieved successfully"
|
// @Success 200 {object} vclaimModels.SepResponse "Data SEP retrieved successfully"
|
||||||
// @Failure 400 {object} gin.H "Invalid request"
|
// @Failure 400 {object} gin.H "Invalid request"
|
||||||
// @Failure 500 {object} gin.H "Internal server error"
|
// @Failure 500 {object} gin.H "Internal server error"
|
||||||
// @Router /sep/{noSep} [get]
|
// @Router /sep/{noSep} [get]
|
||||||
func (h *SepHandler) GetSEP(c *gin.Context) {
|
func (h *SepHandler) GetSEP(c *gin.Context) {
|
||||||
noSep := c.Param("noSep")
|
noSep := c.Param("noSep")
|
||||||
|
|
||||||
if noSep == "" {
|
if noSep == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "noSep required"})
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "noSep required",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(c, 30*time.Second)
|
ctx, cancel := context.WithTimeout(c, 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
endpoint := fmt.Sprintf("/SEP/%s", noSep)
|
endpoint := fmt.Sprintf("SEP/%s", noSep)
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
|
|
||||||
if err := h.service.Get(ctx, endpoint, &result); err != nil {
|
if err := h.service.Get(ctx, endpoint, &result); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "fetch failed", "message": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "fetch failed",
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models.SepResponse{
|
c.JSON(http.StatusOK, vclaimModels.SepResponse{
|
||||||
Message: "Data SEP berhasil diambil",
|
Message: "Data SEP berhasil diambil",
|
||||||
Data: result,
|
Data: result,
|
||||||
})
|
})
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
// DiagnosaResponse represents the response structure for BPJS diagnosa data
|
|
||||||
type DiagnosaResponse struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data map[string]interface{} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiagnosaRawResponse represents the raw response structure from BPJS API
|
|
||||||
type DiagnosaRawResponse struct {
|
|
||||||
MetaData struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
} `json:"metaData"`
|
|
||||||
Response interface{} `json:"response"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// DiagnosaData represents the diagnosa reference data structure
|
|
||||||
type DiagnosaData struct {
|
|
||||||
KdDiag string `json:"kdDiag"`
|
|
||||||
NmDiag string `json:"nmDiag"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiagnosaListResponse represents the response structure for diagnosa list
|
|
||||||
type DiagnosaListResponse struct {
|
|
||||||
Diagnosa []DiagnosaData `json:"diagnosa"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorResponse represents error response structure
|
|
||||||
type ErrorResponse struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Code int `json:"code,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BPJSMetaData represents BPJS API metadata structure
|
|
||||||
type BPJSMetaData struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiagnosaFilter represents filter parameters for diagnosa queries
|
|
||||||
type DiagnosaFilter struct {
|
|
||||||
NIK *string `form:"nik" json:"nik,omitempty"`
|
|
||||||
TglSEP *string `form:"tglSEP" json:"tglSEP,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// ==== REQUEST ====
|
// SepPostRequest represents the request payload for creating a SEP
|
||||||
|
|
||||||
type SepPostRequest struct {
|
type SepPostRequest struct {
|
||||||
TSep TSepPost `json:"t_sep" binding:"required"`
|
TSep TSepPost `json:"tsep" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TSepPost contains the main SEP data for creation
|
||||||
type TSepPost struct {
|
type TSepPost struct {
|
||||||
NoKartu string `json:"noKartu" binding:"required"`
|
NoKartu string `json:"noKartu" binding:"required"`
|
||||||
TglSep string `json:"tglSep" binding:"required"` // yyyy-MM-dd
|
TglSep string `json:"tglSep" binding:"required"` // yyyy-MM-dd
|
||||||
PpkPelayanan string `json:"ppkPelayanan" binding:"required"`
|
PpkPelayanan string `json:"ppkPelayanan" binding:"required"`
|
||||||
JnsPelayanan string `json:"jnsPelayanan" binding:"required"`
|
JnsPelayanan string `json:"jnsPelayanan" binding:"required"`
|
||||||
KlsRawat KlsRawatPost `json:"klsRawat" binding:"required"`
|
KlsRawat KlsRawatPost `json:"klsRawat" binding:"required"`
|
||||||
NoMR string `json:"noMR" binding:"required"`
|
NoMR string `json:"noMR" binding:"required"`
|
||||||
Rujukan Rujukan `json:"rujukan" binding:"required"`
|
Rujukan Rujukan `json:"rujukan" binding:"required"`
|
||||||
Catatan string `json:"catatan"`
|
Catatan string `json:"catatan"`
|
||||||
DiagAwal string `json:"diagAwal" binding:"required"`
|
DiagAwal string `json:"diagAwal" binding:"required"`
|
||||||
Poli Poli `json:"poli" binding:"required"`
|
Poli Poli `json:"poli" binding:"required"`
|
||||||
Cob Flag `json:"cob" binding:"required"`
|
Cob Flag `json:"cob" binding:"required"`
|
||||||
Katarak Flag `json:"katarak" binding:"required"`
|
Katarak Flag `json:"katarak" binding:"required"`
|
||||||
Jaminan Jaminan `json:"jaminan" binding:"required"`
|
Jaminan Jaminan `json:"jaminan" binding:"required"`
|
||||||
TujuanKunj string `json:"tujuanKunj"`
|
TujuanKunj string `json:"tujuanKunj"`
|
||||||
FlagProcedure string `json:"flagProcedure"`
|
FlagProcedure string `json:"flagProcedure"`
|
||||||
KdPenunjang string `json:"kdPenunjang"`
|
KdPenunjang string `json:"kdPenunjang"`
|
||||||
@@ -30,6 +30,7 @@ type TSepPost struct {
|
|||||||
User string `json:"user" binding:"required"`
|
User string `json:"user" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KlsRawatPost represents class of care data for POST requests
|
||||||
type KlsRawatPost struct {
|
type KlsRawatPost struct {
|
||||||
KlsRawatHak string `json:"klsRawatHak" binding:"required"`
|
KlsRawatHak string `json:"klsRawatHak" binding:"required"`
|
||||||
KlsRawatNaik string `json:"klsRawatNaik"`
|
KlsRawatNaik string `json:"klsRawatNaik"`
|
||||||
@@ -37,57 +38,65 @@ type KlsRawatPost struct {
|
|||||||
PenanggungJawab string `json:"penanggungJawab"`
|
PenanggungJawab string `json:"penanggungJawab"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rujukan represents referral data
|
||||||
type Rujukan struct {
|
type Rujukan struct {
|
||||||
AsalRujukan string `json:"asalRujukan" binding:"required"`
|
AsalRujukan string `json:"asalRujukan" binding:"required"`
|
||||||
TglRujukan string `json:"tglRujukan" binding:"required"`
|
TglRujukan string `json:"tglRujukan" binding:"required"`
|
||||||
NoRujukan string `json:"noRujukan" binding:"required"`
|
NoRujukan string `json:"noRujukan" binding:"required"`
|
||||||
PpkRujukan string `json:"ppkRujukan" binding:"required"`
|
PpkRujukan string `json:"ppkRujukan" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Poli represents poly/department data
|
||||||
type Poli struct {
|
type Poli struct {
|
||||||
Tujuan string `json:"tujuan"`
|
Tujuan string `json:"tujuan" binding:"required"`
|
||||||
Eksekutif string `json:"eksekutif" binding:"required"`
|
Eksekutif string `json:"eksekutif" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag represents a generic flag structure
|
||||||
type Flag struct {
|
type Flag struct {
|
||||||
Flag string `json:"cob,omitempty" json:"katarak,omitempty" binding:"required"`
|
Flag string `json:"flag" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Jaminan represents insurance guarantee data
|
||||||
type Jaminan struct {
|
type Jaminan struct {
|
||||||
LakaLantas string `json:"lakaLantas" binding:"required"`
|
LakaLantas string `json:"lakaLantas" binding:"required"`
|
||||||
NoLP string `json:"noLP"`
|
NoLP string `json:"noLP"`
|
||||||
Penjamin Penjamin `json:"penjamin"`
|
Penjamin Penjamin `json:"penjamin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Penjamin represents guarantor data
|
||||||
type Penjamin struct {
|
type Penjamin struct {
|
||||||
TglKejadian string `json:"tglKejadian"`
|
TglKejadian string `json:"tglKejadian"`
|
||||||
Keterangan string `json:"keterangan"`
|
Keterangan string `json:"keterangan"`
|
||||||
Suplesi Suplesi `json:"suplesi"`
|
Suplesi Suplesi `json:"suplesi"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Suplesi represents supplementary data
|
||||||
type Suplesi struct {
|
type Suplesi struct {
|
||||||
Suplesi string `json:"suplesi"`
|
Suplesi string `json:"suplesi"`
|
||||||
NoSepSuplesi string `json:"noSepSuplesi"`
|
NoSepSuplesi string `json:"noSepSuplesi"`
|
||||||
LokasiLaka LokasiLaka `json:"lokasiLaka"`
|
LokasiLaka LokasiLaka `json:"lokasiLaka"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LokasiLaka represents accident location data
|
||||||
type LokasiLaka struct {
|
type LokasiLaka struct {
|
||||||
KdPropinsi string `json:"kdPropinsi"`
|
KdPropinsi string `json:"kdPropinsi"`
|
||||||
KdKabupaten string `json:"kdKabupaten"`
|
KdKabupaten string `json:"kdKabupaten"`
|
||||||
KdKecamatan string `json:"kdKecamatan"`
|
KdKecamatan string `json:"kdKecamatan"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skdp represents SKDP data
|
||||||
type Skdp struct {
|
type Skdp struct {
|
||||||
NoSurat string `json:"noSurat" binding:"required"`
|
NoSurat string `json:"noSurat" binding:"required"`
|
||||||
KodeDPJP string `json:"kodeDPJP" binding:"required"`
|
KodeDPJP string `json:"kodeDPJP" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== UPDATE ====
|
// SepPutRequest represents the request payload for updating a SEP
|
||||||
|
|
||||||
type SepPutRequest struct {
|
type SepPutRequest struct {
|
||||||
TSep TSepPut `json:"t_sep" binding:"required"`
|
TSep TSepPut `json:"tsep" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TSepPut contains the main SEP data for updates
|
||||||
type TSepPut struct {
|
type TSepPut struct {
|
||||||
NoSep string `json:"noSep" binding:"required"`
|
NoSep string `json:"noSep" binding:"required"`
|
||||||
KlsRawat KlsRawatPut `json:"klsRawat"`
|
KlsRawat KlsRawatPut `json:"klsRawat"`
|
||||||
@@ -103,6 +112,7 @@ type TSepPut struct {
|
|||||||
User string `json:"user" binding:"required"`
|
User string `json:"user" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KlsRawatPut represents class of care data for PUT requests
|
||||||
type KlsRawatPut struct {
|
type KlsRawatPut struct {
|
||||||
KlsRawatHak string `json:"klsRawatHak"`
|
KlsRawatHak string `json:"klsRawatHak"`
|
||||||
KlsRawatNaik string `json:"klsRawatNaik"`
|
KlsRawatNaik string `json:"klsRawatNaik"`
|
||||||
@@ -110,22 +120,21 @@ type KlsRawatPut struct {
|
|||||||
PenanggungJawab string `json:"penanggungJawab"`
|
PenanggungJawab string `json:"penanggungJawab"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== DELETE ====
|
// SepDeleteRequest represents the request payload for deleting a SEP
|
||||||
|
|
||||||
type SepDeleteRequest struct {
|
type SepDeleteRequest struct {
|
||||||
TSep struct {
|
TSep struct {
|
||||||
NoSep string `json:"noSep" binding:"required"`
|
NoSep string `json:"noSep" binding:"required"`
|
||||||
User string `json:"user" binding:"required"`
|
User string `json:"user" binding:"required"`
|
||||||
} `json:"t_sep" binding:"required"`
|
} `json:"tsep" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== RESPONSE ====
|
// SepResponse represents the standard response for SEP operations
|
||||||
|
|
||||||
type SepResponse struct {
|
type SepResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Data map[string]interface{} `json:"data,omitempty"`
|
Data map[string]interface{} `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SepRawResponse represents the raw response from BPJS API
|
||||||
type SepRawResponse struct {
|
type SepRawResponse struct {
|
||||||
MetaData struct {
|
MetaData struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
@@ -2,18 +2,17 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"api-service/internal/config"
|
"api-service/internal/config"
|
||||||
|
"api-service/internal/database"
|
||||||
authHandlers "api-service/internal/handlers/auth"
|
authHandlers "api-service/internal/handlers/auth"
|
||||||
bpjsPesertaHandlers "api-service/internal/handlers/bpjs/reference"
|
healthcheckHandlers "api-service/internal/handlers/healthcheck"
|
||||||
|
bpjsPesertaHandlers "api-service/internal/handlers/reference"
|
||||||
retribusiHandlers "api-service/internal/handlers/retribusi"
|
retribusiHandlers "api-service/internal/handlers/retribusi"
|
||||||
satusehatHandlers "api-service/internal/handlers/satusehat"
|
swaggerHandlers "api-service/internal/handlers/swagger"
|
||||||
"api-service/internal/middleware"
|
"api-service/internal/middleware"
|
||||||
services "api-service/internal/services/auth"
|
services "api-service/internal/services/auth"
|
||||||
satusehatServices "api-service/internal/services/satusehat"
|
|
||||||
"api-service/pkg/logger"
|
"api-service/pkg/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
swaggerFiles "github.com/swaggo/files"
|
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||||
@@ -34,14 +33,19 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
|||||||
logger.Fatal("Failed to initialize auth service")
|
logger.Fatal("Failed to initialize auth service")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize SatuSehat service
|
// Initialize database service for health check
|
||||||
satusehatService := satusehatServices.NewSatuSehatService(&cfg.SatuSehat)
|
dbService := database.New(cfg)
|
||||||
if satusehatService == nil {
|
|
||||||
logger.Fatal("Failed to initialize SatuSehat service")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swagger UI route
|
// Health check endpoint
|
||||||
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
healthCheckHandler := healthcheckHandlers.NewHealthCheckHandler(dbService)
|
||||||
|
sistem := router.Group("/api/sistem")
|
||||||
|
sistem.GET("/health", healthCheckHandler.CheckHealth)
|
||||||
|
|
||||||
|
// Initialize Swagger handler
|
||||||
|
swaggerHandler := swaggerHandlers.NewHandler(cfg)
|
||||||
|
|
||||||
|
// Register Swagger routes
|
||||||
|
swaggerHandler.RegisterRoutes(router)
|
||||||
|
|
||||||
// API v1 group
|
// API v1 group
|
||||||
v1 := router.Group("/api/v1")
|
v1 := router.Group("/api/v1")
|
||||||
@@ -67,16 +71,6 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
|||||||
bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs)
|
bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs)
|
||||||
v1.GET("/bpjs/peserta/nik/:nik/tglSEP/:tglSEP", bpjsPesertaHandler.GetPesertaByNIK)
|
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 ===============================================
|
// ============= PUBLISHED ROUTES ===============================================
|
||||||
|
|
||||||
// // Retribusi endpoints
|
// // Retribusi endpoints
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"api-service/internal/config"
|
"api-service/internal/config"
|
||||||
|
|
||||||
"github.com/mashingan/smapping"
|
"github.com/mashingan/smapping"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VClaimService interface for VClaim operations
|
// VClaimService interface for VClaim operations
|
||||||
@@ -49,6 +51,11 @@ type ResponDTO struct {
|
|||||||
|
|
||||||
// NewService creates a new VClaim service instance
|
// NewService creates a new VClaim service instance
|
||||||
func NewService(cfg config.BpjsConfig) VClaimService {
|
func NewService(cfg config.BpjsConfig) VClaimService {
|
||||||
|
log.Info().
|
||||||
|
Str("base_url", cfg.BaseURL).
|
||||||
|
Dur("timeout", cfg.Timeout).
|
||||||
|
Msg("Creating new VClaim service instance")
|
||||||
|
|
||||||
service := &Service{
|
service := &Service{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
@@ -88,8 +95,20 @@ func (s *Service) SetHTTPClient(client *http.Client) {
|
|||||||
// prepareRequest prepares HTTP request with required headers
|
// prepareRequest prepares HTTP request with required headers
|
||||||
func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, body io.Reader) (*http.Request, error) {
|
func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, body io.Reader) (*http.Request, error) {
|
||||||
fullURL := s.config.BaseURL + endpoint
|
fullURL := s.config.BaseURL + endpoint
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("method", method).
|
||||||
|
Str("endpoint", endpoint).
|
||||||
|
Str("full_url", fullURL).
|
||||||
|
Msg("Preparing HTTP request")
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, fullURL, body)
|
req, err := http.NewRequestWithContext(ctx, method, fullURL, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("method", method).
|
||||||
|
Str("endpoint", endpoint).
|
||||||
|
Msg("Failed to create HTTP request")
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +121,14 @@ func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, b
|
|||||||
req.Header.Set("X-signature", xSignature)
|
req.Header.Set("X-signature", xSignature)
|
||||||
req.Header.Set("user_key", userKey)
|
req.Header.Set("user_key", userKey)
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("method", method).
|
||||||
|
Str("endpoint", endpoint).
|
||||||
|
Str("x_cons_id", consID).
|
||||||
|
Str("x_timestamp", tstamp).
|
||||||
|
Str("user_key", userKey).
|
||||||
|
Msg("Request headers set")
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,22 +136,55 @@ func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, b
|
|||||||
func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) {
|
func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) {
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Int("status_code", res.StatusCode).
|
||||||
|
Str("status", res.Status).
|
||||||
|
Msg("Processing HTTP response")
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Int("status_code", res.StatusCode).
|
||||||
|
Msg("Failed to read response body")
|
||||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log response body for debugging (truncate if too long)
|
||||||
|
bodyStr := string(body)
|
||||||
|
if len(bodyStr) > 1000 {
|
||||||
|
bodyStr = bodyStr[:1000] + "...(truncated)"
|
||||||
|
}
|
||||||
|
log.Debug().
|
||||||
|
Int("status_code", res.StatusCode).
|
||||||
|
Str("response_body", bodyStr).
|
||||||
|
Msg("Raw response received")
|
||||||
|
|
||||||
// Check HTTP status
|
// Check HTTP status
|
||||||
if res.StatusCode >= 400 {
|
if res.StatusCode >= 400 {
|
||||||
|
log.Error().
|
||||||
|
Int("status_code", res.StatusCode).
|
||||||
|
Str("response_body", bodyStr).
|
||||||
|
Msg("HTTP error response")
|
||||||
return nil, fmt.Errorf("HTTP error: %d - %s", res.StatusCode, string(body))
|
return nil, fmt.Errorf("HTTP error: %d - %s", res.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse raw response
|
// Parse raw response
|
||||||
var respMentah ResponMentahDTO
|
var respMentah ResponMentahDTO
|
||||||
if err := json.Unmarshal(body, &respMentah); err != nil {
|
if err := json.Unmarshal(body, &respMentah); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Int("status_code", res.StatusCode).
|
||||||
|
Msg("Failed to unmarshal raw response")
|
||||||
return nil, fmt.Errorf("failed to unmarshal raw response: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal raw response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log metadata
|
||||||
|
log.Info().
|
||||||
|
Str("meta_code", respMentah.MetaData.Code).
|
||||||
|
Str("meta_message", respMentah.MetaData.Message).
|
||||||
|
Msg("Response metadata")
|
||||||
|
|
||||||
// Create final response
|
// Create final response
|
||||||
finalResp := &ResponDTO{
|
finalResp := &ResponDTO{
|
||||||
MetaData: respMentah.MetaData,
|
MetaData: respMentah.MetaData,
|
||||||
@@ -132,6 +192,7 @@ func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) {
|
|||||||
|
|
||||||
// If response is empty, return as is
|
// If response is empty, return as is
|
||||||
if respMentah.Response == "" {
|
if respMentah.Response == "" {
|
||||||
|
log.Debug().Msg("Empty response received, returning metadata only")
|
||||||
return finalResp, nil
|
return finalResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,17 +200,47 @@ func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) {
|
|||||||
consID, secretKey, _, tstamp, _ := s.config.SetHeader()
|
consID, secretKey, _, tstamp, _ := s.config.SetHeader()
|
||||||
respDecrypt, err := ResponseVclaim(respMentah.Response, consID+secretKey+tstamp)
|
respDecrypt, err := ResponseVclaim(respMentah.Response, consID+secretKey+tstamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("meta_code", respMentah.MetaData.Code).
|
||||||
|
Msg("Failed to decrypt response")
|
||||||
return nil, fmt.Errorf("failed to decrypt response: %w", err)
|
return nil, fmt.Errorf("failed to decrypt response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("encrypted_length", fmt.Sprintf("%d bytes", len(respMentah.Response))).
|
||||||
|
Str("decrypted_length", fmt.Sprintf("%d bytes", len(respDecrypt))).
|
||||||
|
Msg("Response decrypted successfully")
|
||||||
|
|
||||||
// Unmarshal decrypted response
|
// Unmarshal decrypted response
|
||||||
if respDecrypt != "" {
|
if respDecrypt != "" {
|
||||||
if err := json.Unmarshal([]byte(respDecrypt), &finalResp.Response); err != nil {
|
if err := json.Unmarshal([]byte(respDecrypt), &finalResp.Response); err != nil {
|
||||||
// If JSON unmarshal fails, store as string
|
// If JSON unmarshal fails, store as string
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to unmarshal decrypted response, storing as string")
|
||||||
finalResp.Response = respDecrypt
|
finalResp.Response = respDecrypt
|
||||||
|
} else {
|
||||||
|
log.Debug().Msg("Decrypted response unmarshaled successfully")
|
||||||
|
|
||||||
|
// Use gjson to extract and log some metadata from the response if it's JSON
|
||||||
|
if jsonBytes, err := json.Marshal(finalResp.Response); err == nil {
|
||||||
|
jsonStr := string(jsonBytes)
|
||||||
|
// Extract some common fields using gjson
|
||||||
|
if metaCode := gjson.Get(jsonStr, "metaData.code"); metaCode.Exists() {
|
||||||
|
log.Info().
|
||||||
|
Str("response_meta_code", metaCode.String()).
|
||||||
|
Msg("Final response metadata")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("meta_code", finalResp.MetaData.Code).
|
||||||
|
Str("meta_message", finalResp.MetaData.Message).
|
||||||
|
Msg("Response processing completed")
|
||||||
|
|
||||||
return finalResp, nil
|
return finalResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
676
internal/services/satusehat/satusehatBridge.go
Normal file
676
internal/services/satusehat/satusehatBridge.go
Normal file
@@ -0,0 +1,676 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"api-service/internal/config"
|
||||||
|
"api-service/pkg/logger"
|
||||||
|
|
||||||
|
"github.com/mashingan/smapping"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SatuSehatService interface for SATUSEHAT operations
|
||||||
|
type SatuSehatService interface {
|
||||||
|
// Standard HTTP methods
|
||||||
|
Get(ctx context.Context, endpoint string, result interface{}) error
|
||||||
|
Post(ctx context.Context, endpoint string, payload interface{}, result interface{}) error
|
||||||
|
Put(ctx context.Context, endpoint string, payload interface{}, result interface{}) error
|
||||||
|
Delete(ctx context.Context, endpoint string, result interface{}) error
|
||||||
|
|
||||||
|
// Raw response methods
|
||||||
|
GetRawResponse(ctx context.Context, endpoint string) (*SatuSehatResponDTO, error)
|
||||||
|
PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*SatuSehatResponDTO, error)
|
||||||
|
|
||||||
|
// FHIR specific methods
|
||||||
|
PostBundle(ctx context.Context, bundle interface{}) (*SatuSehatResponDTO, error)
|
||||||
|
GetPatientByNIK(ctx context.Context, nik string) (*SatuSehatResponDTO, error)
|
||||||
|
GetPractitionerByNIK(ctx context.Context, nik string) (*SatuSehatResponDTO, error)
|
||||||
|
GetResourceByID(ctx context.Context, resourceType, id string) (*SatuSehatResponDTO, error)
|
||||||
|
|
||||||
|
// Token management
|
||||||
|
RefreshToken(ctx context.Context) error
|
||||||
|
IsTokenValid() bool
|
||||||
|
GenerateToken(ctx context.Context, clientID, clientSecret string) (*SatuSehatResponDTO, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SatuSehatService struct for SATUSEHAT service
|
||||||
|
type SatuSehatServiceStruct struct {
|
||||||
|
config config.SatuSehatConfig
|
||||||
|
httpClient *http.Client
|
||||||
|
token TokenDetail
|
||||||
|
tokenMutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token detail structure
|
||||||
|
type TokenDetail struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
IssuedAt int64 `json:"issued_at"`
|
||||||
|
OrganizationName string `json:"organization_name"`
|
||||||
|
DeveloperEmail string `json:"developer.email"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ApplicationName string `json:"application_name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
ExpiryTime time.Time `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response structures
|
||||||
|
type SatuSehatResponMentahDTO struct {
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SatuSehatResponDTO struct {
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
Error *ErrorInfo `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorInfo struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Details string `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token methods
|
||||||
|
func (t *TokenDetail) IsExpired() bool {
|
||||||
|
if t.ExpiryTime.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return time.Now().UTC().After(t.ExpiryTime.Add(-5 * time.Minute))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenDetail) SetExpired() {
|
||||||
|
t.ExpiryTime = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSatuSehatService creates a new SATUSEHAT service instance
|
||||||
|
func NewSatuSehatService(cfg config.SatuSehatConfig) SatuSehatService {
|
||||||
|
service := &SatuSehatServiceStruct{
|
||||||
|
config: cfg,
|
||||||
|
httpClient: &http.Client{
|
||||||
|
Timeout: cfg.Timeout,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSatuSehatServiceFromConfig creates service from main config
|
||||||
|
func NewSatuSehatServiceFromConfig(cfg *config.Config) SatuSehatService {
|
||||||
|
return NewSatuSehatService(cfg.SatuSehat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSatuSehatServiceFromInterface creates service from interface (for backward compatibility)
|
||||||
|
func NewSatuSehatServiceFromInterface(cfg interface{}) (SatuSehatService, error) {
|
||||||
|
var satusehatConfig config.SatuSehatConfig
|
||||||
|
|
||||||
|
// Try to map from interface
|
||||||
|
err := smapping.FillStruct(&satusehatConfig, smapping.MapFields(&cfg))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to map config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if satusehatConfig.Timeout == 0 {
|
||||||
|
satusehatConfig.Timeout = 30 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewSatuSehatService(satusehatConfig), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTPClient allows custom http client configuration
|
||||||
|
func (s *SatuSehatServiceStruct) SetHTTPClient(client *http.Client) {
|
||||||
|
s.httpClient = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshToken obtains new access token
|
||||||
|
func (s *SatuSehatServiceStruct) RefreshToken(ctx context.Context) error {
|
||||||
|
s.tokenMutex.Lock()
|
||||||
|
defer s.tokenMutex.Unlock()
|
||||||
|
|
||||||
|
// Double-check pattern
|
||||||
|
if !s.token.IsExpired() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicate /oauth2/v1 from URL since AuthURL already contains it
|
||||||
|
tokenURL := fmt.Sprintf("%s/accesstoken?grant_type=client_credentials", s.config.AuthURL)
|
||||||
|
|
||||||
|
formData := fmt.Sprintf("client_id=%s&client_secret=%s", s.config.ClientID, s.config.ClientSecret)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", tokenURL, bytes.NewBufferString(formData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create token request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
res, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute token request: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read token response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
// Log the error response for debugging
|
||||||
|
fmt.Printf("DEBUG: Token request failed with status %d: %s\n", res.StatusCode, string(body))
|
||||||
|
return fmt.Errorf("token request failed with status %d: %s", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: log the raw response for troubleshooting
|
||||||
|
fmt.Printf("DEBUG: SATUSEHAT token response - Status: %d, Body: %s\n", res.StatusCode, string(body))
|
||||||
|
fmt.Printf("DEBUG: Request URL: %s\n", tokenURL)
|
||||||
|
fmt.Printf("DEBUG: Request Headers: %+v\n", req.Header)
|
||||||
|
|
||||||
|
return s.parseTokenResponse(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTokenResponse parses token response from SATUSEHAT
|
||||||
|
func (s *SatuSehatServiceStruct) parseTokenResponse(body []byte) error {
|
||||||
|
// Debug: log the raw response for detailed analysis
|
||||||
|
fmt.Printf("DEBUG: Raw token response body: %s\n", string(body))
|
||||||
|
|
||||||
|
result := gjson.ParseBytes(body)
|
||||||
|
|
||||||
|
// Check if we have a valid access token
|
||||||
|
accessToken := result.Get("access_token").String()
|
||||||
|
if accessToken == "" {
|
||||||
|
return fmt.Errorf("no access token found in response: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
issuedAt := result.Get("issued_at").Int()
|
||||||
|
expiresIn := result.Get("expires_in").Int()
|
||||||
|
|
||||||
|
// Handle timestamp conversion (issued_at could be in milliseconds or seconds)
|
||||||
|
var expiryTime time.Time
|
||||||
|
if issuedAt > 1000000000000 { // If timestamp is in milliseconds
|
||||||
|
expiryTime = time.Unix(issuedAt/1000, 0).Add(time.Duration(expiresIn) * time.Second)
|
||||||
|
} else if issuedAt > 0 { // If timestamp is in seconds
|
||||||
|
expiryTime = time.Unix(issuedAt, 0).Add(time.Duration(expiresIn) * time.Second)
|
||||||
|
} else {
|
||||||
|
// If no issued_at, use current time + expires_in
|
||||||
|
expiryTime = time.Now().UTC().Add(time.Duration(expiresIn) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.token = TokenDetail{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
TokenType: result.Get("token_type").String(),
|
||||||
|
ExpiresIn: expiresIn,
|
||||||
|
IssuedAt: issuedAt,
|
||||||
|
OrganizationName: result.Get("organization_name").String(),
|
||||||
|
DeveloperEmail: result.Get("developer\\.email").String(),
|
||||||
|
ClientID: result.Get("client_id").String(),
|
||||||
|
ApplicationName: result.Get("application_name").String(),
|
||||||
|
Status: result.Get("status").String(),
|
||||||
|
ExpiryTime: expiryTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("SATUSEHAT token refreshed successfully", map[string]interface{}{
|
||||||
|
"expires_at": s.token.ExpiryTime,
|
||||||
|
"organization": s.token.OrganizationName,
|
||||||
|
"token_type": s.token.TokenType,
|
||||||
|
"client_id": s.token.ClientID,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTokenValid checks if current token is valid
|
||||||
|
func (s *SatuSehatServiceStruct) IsTokenValid() bool {
|
||||||
|
s.tokenMutex.RLock()
|
||||||
|
defer s.tokenMutex.RUnlock()
|
||||||
|
return !s.token.IsExpired()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureValidToken ensures we have a valid token
|
||||||
|
func (s *SatuSehatServiceStruct) ensureValidToken(ctx context.Context) error {
|
||||||
|
s.tokenMutex.RLock()
|
||||||
|
needsRefresh := s.token.IsExpired()
|
||||||
|
s.tokenMutex.RUnlock()
|
||||||
|
|
||||||
|
if needsRefresh {
|
||||||
|
return s.RefreshToken(ctx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareRequest prepares HTTP request with required headers
|
||||||
|
func (s *SatuSehatServiceStruct) prepareRequest(ctx context.Context, method, endpoint string, body io.Reader) (*http.Request, error) {
|
||||||
|
// Ensure valid token
|
||||||
|
if err := s.ensureValidToken(ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to ensure valid token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullURL := s.config.BaseURL + endpoint
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, fullURL, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
s.tokenMutex.RLock()
|
||||||
|
token := s.token.AccessToken
|
||||||
|
s.tokenMutex.RUnlock()
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processResponse processes response from SATUSEHAT API
|
||||||
|
func (s *SatuSehatServiceStruct) processResponse(res *http.Response) (*SatuSehatResponDTO, error) {
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create response
|
||||||
|
resp := &SatuSehatResponDTO{
|
||||||
|
StatusCode: res.StatusCode,
|
||||||
|
Success: res.StatusCode >= 200 && res.StatusCode < 300,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle different status codes
|
||||||
|
switch {
|
||||||
|
case res.StatusCode == 401:
|
||||||
|
s.tokenMutex.Lock()
|
||||||
|
s.token.SetExpired()
|
||||||
|
s.tokenMutex.Unlock()
|
||||||
|
|
||||||
|
resp.Error = &ErrorInfo{
|
||||||
|
Code: "UNAUTHORIZED",
|
||||||
|
Details: "Token expired or invalid",
|
||||||
|
}
|
||||||
|
resp.Message = "Unauthorized access"
|
||||||
|
|
||||||
|
case res.StatusCode >= 400 && res.StatusCode < 500:
|
||||||
|
resp.Error = &ErrorInfo{
|
||||||
|
Code: "CLIENT_ERROR",
|
||||||
|
Details: string(body),
|
||||||
|
}
|
||||||
|
resp.Message = "Client error"
|
||||||
|
|
||||||
|
case res.StatusCode >= 500:
|
||||||
|
resp.Error = &ErrorInfo{
|
||||||
|
Code: "SERVER_ERROR",
|
||||||
|
Details: string(body),
|
||||||
|
}
|
||||||
|
resp.Message = "Server error"
|
||||||
|
|
||||||
|
default:
|
||||||
|
resp.Message = "Success"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON response if successful
|
||||||
|
if resp.Success && len(body) > 0 {
|
||||||
|
var jsonData interface{}
|
||||||
|
if err := json.Unmarshal(body, &jsonData); err != nil {
|
||||||
|
// If JSON unmarshal fails, store as string
|
||||||
|
resp.Data = string(body)
|
||||||
|
} else {
|
||||||
|
resp.Data = jsonData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get performs HTTP GET request
|
||||||
|
func (s *SatuSehatServiceStruct) Get(ctx context.Context, endpoint string, result interface{}) error {
|
||||||
|
resp, err := s.GetRawResponse(ctx, endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapToResult(resp, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post performs HTTP POST request
|
||||||
|
func (s *SatuSehatServiceStruct) Post(ctx context.Context, endpoint string, payload interface{}, result interface{}) error {
|
||||||
|
resp, err := s.PostRawResponse(ctx, endpoint, payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapToResult(resp, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put performs HTTP PUT request
|
||||||
|
func (s *SatuSehatServiceStruct) Put(ctx context.Context, endpoint string, payload interface{}, result interface{}) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if payload != nil {
|
||||||
|
if err := json.NewEncoder(&buf).Encode(payload); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode payload: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := s.prepareRequest(ctx, http.MethodPut, endpoint, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute PUT request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.processResponse(res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapToResult(resp, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete performs HTTP DELETE request
|
||||||
|
func (s *SatuSehatServiceStruct) Delete(ctx context.Context, endpoint string, result interface{}) error {
|
||||||
|
req, err := s.prepareRequest(ctx, http.MethodDelete, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute DELETE request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.processResponse(res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapToResult(resp, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawResponse returns raw response without mapping
|
||||||
|
func (s *SatuSehatServiceStruct) GetRawResponse(ctx context.Context, endpoint string) (*SatuSehatResponDTO, error) {
|
||||||
|
req, err := s.prepareRequest(ctx, http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute GET request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.processResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostRawResponse returns raw response without mapping
|
||||||
|
func (s *SatuSehatServiceStruct) PostRawResponse(ctx context.Context, endpoint string, payload interface{}) (*SatuSehatResponDTO, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if payload != nil {
|
||||||
|
if err := json.NewEncoder(&buf).Encode(payload); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to encode payload: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := s.prepareRequest(ctx, http.MethodPost, endpoint, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute POST request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.processResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FHIR-specific methods
|
||||||
|
|
||||||
|
// PostBundle posts FHIR bundle to SATUSEHAT
|
||||||
|
func (s *SatuSehatServiceStruct) PostBundle(ctx context.Context, bundle interface{}) (*SatuSehatResponDTO, error) {
|
||||||
|
return s.PostRawResponse(ctx, "", bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPatientByNIK retrieves patient by NIK
|
||||||
|
func (s *SatuSehatServiceStruct) GetPatientByNIK(ctx context.Context, nik string) (*SatuSehatResponDTO, error) {
|
||||||
|
endpoint := fmt.Sprintf("/Patient?identifier=https://fhir.kemkes.go.id/id/nik|%s", nik)
|
||||||
|
return s.GetRawResponse(ctx, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPractitionerByNIK retrieves practitioner by NIK
|
||||||
|
func (s *SatuSehatServiceStruct) GetPractitionerByNIK(ctx context.Context, nik string) (*SatuSehatResponDTO, error) {
|
||||||
|
endpoint := fmt.Sprintf("/Practitioner?identifier=https://fhir.kemkes.go.id/id/nik|%s", nik)
|
||||||
|
return s.GetRawResponse(ctx, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResourceByID retrieves any FHIR resource by ID
|
||||||
|
func (s *SatuSehatServiceStruct) GetResourceByID(ctx context.Context, resourceType, id string) (*SatuSehatResponDTO, error) {
|
||||||
|
endpoint := fmt.Sprintf("/%s/%s", resourceType, id)
|
||||||
|
return s.GetRawResponse(ctx, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateToken generates a new access token with custom client credentials
|
||||||
|
func (s *SatuSehatServiceStruct) GenerateToken(ctx context.Context, clientID, clientSecret string) (*SatuSehatResponDTO, error) {
|
||||||
|
// Remove duplicate /oauth2/v1 from URL since AuthURL already contains it
|
||||||
|
tokenURL := fmt.Sprintf("%s/accesstoken?grant_type=client_credentials", s.config.AuthURL)
|
||||||
|
|
||||||
|
formData := fmt.Sprintf("client_id=%s&client_secret=%s", clientID, clientSecret)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", tokenURL, strings.NewReader(formData))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create token request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
res, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute token request: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read token response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the response using the existing response processor
|
||||||
|
resp := &SatuSehatResponDTO{
|
||||||
|
StatusCode: res.StatusCode,
|
||||||
|
Success: res.StatusCode >= 200 && res.StatusCode < 300,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle different status codes
|
||||||
|
switch {
|
||||||
|
case res.StatusCode == 401:
|
||||||
|
resp.Error = &ErrorInfo{
|
||||||
|
Code: "UNAUTHORIZED",
|
||||||
|
Details: "Invalid client credentials",
|
||||||
|
}
|
||||||
|
resp.Message = "Unauthorized access"
|
||||||
|
|
||||||
|
case res.StatusCode >= 400 && res.StatusCode < 500:
|
||||||
|
resp.Error = &ErrorInfo{
|
||||||
|
Code: "CLIENT_ERROR",
|
||||||
|
Details: string(body),
|
||||||
|
}
|
||||||
|
resp.Message = "Client error"
|
||||||
|
|
||||||
|
case res.StatusCode >= 500:
|
||||||
|
resp.Error = &ErrorInfo{
|
||||||
|
Code: "SERVER_ERROR",
|
||||||
|
Details: string(body),
|
||||||
|
}
|
||||||
|
resp.Message = "Server error"
|
||||||
|
|
||||||
|
default:
|
||||||
|
resp.Message = "Success"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON response if successful
|
||||||
|
if resp.Success && len(body) > 0 {
|
||||||
|
var jsonData interface{}
|
||||||
|
if err := json.Unmarshal(body, &jsonData); err != nil {
|
||||||
|
// If JSON unmarshal fails, store as string
|
||||||
|
resp.Data = string(body)
|
||||||
|
} else {
|
||||||
|
resp.Data = jsonData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
// mapToResult maps the final response to the result interface
|
||||||
|
func mapToResult(resp *SatuSehatResponDTO, result interface{}) error {
|
||||||
|
respBytes, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal final response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(respBytes, result); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal to result: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility functions
|
||||||
|
|
||||||
|
func SatuSehatGetRequest(endpoint string, cfg interface{}) interface{} {
|
||||||
|
service, err := NewSatuSehatServiceFromInterface(cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to create SATUSEHAT service", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := service.GetRawResponse(ctx, endpoint)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to get SATUSEHAT response", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func SatuSehatPostRequest(endpoint string, cfg interface{}, data interface{}) interface{} {
|
||||||
|
service, err := NewSatuSehatServiceFromInterface(cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to create SATUSEHAT service", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := service.PostRawResponse(ctx, endpoint, data)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to post SATUSEHAT response", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// FHIR helper functions
|
||||||
|
|
||||||
|
func SatuSehatGetPatient(nik string, cfg interface{}) interface{} {
|
||||||
|
service, err := NewSatuSehatServiceFromInterface(cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to create SATUSEHAT service", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := service.GetPatientByNIK(ctx, nik)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to get patient", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"nik": nik,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func SatuSehatGetPractitioner(nik string, cfg interface{}) interface{} {
|
||||||
|
service, err := NewSatuSehatServiceFromInterface(cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to create SATUSEHAT service", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := service.GetPractitionerByNIK(ctx, nik)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to get practitioner", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"nik": nik,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func SatuSehatPostBundle(bundle interface{}, cfg interface{}) interface{} {
|
||||||
|
service, err := NewSatuSehatServiceFromInterface(cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to create SATUSEHAT service", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := service.PostBundle(ctx, bundle)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to post bundle", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
@@ -1,350 +0,0 @@
|
|||||||
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 ""
|
|
||||||
}
|
|
||||||
34
test_swagger_env.go
Normal file
34
test_swagger_env.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Set environment variables for testing
|
||||||
|
os.Setenv("SWAGGER_TITLE", "My Custom API Service")
|
||||||
|
os.Setenv("SWAGGER_DESCRIPTION", "This is a custom API service for managing various resources")
|
||||||
|
os.Setenv("SWAGGER_VERSION", "2.0.0")
|
||||||
|
os.Setenv("SWAGGER_CONTACT_NAME", "Support Team")
|
||||||
|
os.Setenv("SWAGGER_CONTACT_URL", "https://mycompany.com/support")
|
||||||
|
os.Setenv("SWAGGER_CONTACT_EMAIL", "support@mycompany.com")
|
||||||
|
os.Setenv("SWAGGER_LICENSE_NAME", "MIT License")
|
||||||
|
os.Setenv("SWAGGER_LICENSE_URL", "https://opensource.org/licenses/MIT")
|
||||||
|
os.Setenv("SWAGGER_HOST", "api.mycompany.com:8080")
|
||||||
|
os.Setenv("SWAGGER_BASE_PATH", "/api/v2")
|
||||||
|
os.Setenv("SWAGGER_SCHEMES", "https")
|
||||||
|
|
||||||
|
fmt.Println("Environment variables set for Swagger documentation:")
|
||||||
|
fmt.Println("SWAGGER_TITLE:", os.Getenv("SWAGGER_TITLE"))
|
||||||
|
fmt.Println("SWAGGER_DESCRIPTION:", os.Getenv("SWAGGER_DESCRIPTION"))
|
||||||
|
fmt.Println("SWAGGER_VERSION:", os.Getenv("SWAGGER_VERSION"))
|
||||||
|
fmt.Println("SWAGGER_CONTACT_NAME:", os.Getenv("SWAGGER_CONTACT_NAME"))
|
||||||
|
fmt.Println("SWAGGER_HOST:", os.Getenv("SWAGGER_HOST"))
|
||||||
|
fmt.Println("SWAGGER_BASE_PATH:", os.Getenv("SWAGGER_BASE_PATH"))
|
||||||
|
fmt.Println("SWAGGER_SCHEMES:", os.Getenv("SWAGGER_SCHEMES"))
|
||||||
|
|
||||||
|
fmt.Println("\nTo test the Swagger generation, run:")
|
||||||
|
fmt.Println("swag init -g cmd/api/main.go --parseDependency --parseInternal")
|
||||||
|
fmt.Println("Then check docs/docs.go to see the updated values")
|
||||||
|
}
|
||||||
@@ -13,8 +13,7 @@ type HandlerData struct {
|
|||||||
Name string
|
Name string
|
||||||
NameLower string
|
NameLower string
|
||||||
NamePlural string
|
NamePlural string
|
||||||
Category string
|
Category string // Untuk backward compatibility (bagian pertama)
|
||||||
CategoryPath string
|
|
||||||
ModuleName string
|
ModuleName string
|
||||||
TableName string
|
TableName string
|
||||||
HasGet bool
|
HasGet bool
|
||||||
@@ -48,16 +47,18 @@ func main() {
|
|||||||
methods = []string{"get", "post", "put", "delete", "dynamic", "search"}
|
methods = []string{"get", "post", "put", "delete", "dynamic", "search"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse category and entity
|
// Parse category and entity - support up to 4 levels
|
||||||
var category, entityName string
|
var category, entityName string
|
||||||
if strings.Contains(entityPath, "/") {
|
if strings.Contains(entityPath, "/") {
|
||||||
parts := strings.Split(entityPath, "/")
|
parts := strings.Split(entityPath, "/")
|
||||||
if len(parts) != 2 {
|
if len(parts) < 2 || len(parts) > 4 {
|
||||||
fmt.Println("❌ Error: Invalid path format. Use 'category/entity' or just 'entity'")
|
fmt.Println("❌ Error: Invalid path format. Use up to 4 levels like 'level1/level2/level3/entity'")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
// Entity name is the last part
|
||||||
|
entityName = parts[len(parts)-1]
|
||||||
|
// Category for backward compatibility (first part)
|
||||||
category = parts[0]
|
category = parts[0]
|
||||||
entityName = parts[1]
|
|
||||||
} else {
|
} else {
|
||||||
category = ""
|
category = ""
|
||||||
entityName = entityPath
|
entityName = entityPath
|
||||||
@@ -81,7 +82,6 @@ func main() {
|
|||||||
NameLower: entityLower,
|
NameLower: entityLower,
|
||||||
NamePlural: entityPlural,
|
NamePlural: entityPlural,
|
||||||
Category: category,
|
Category: category,
|
||||||
CategoryPath: category,
|
|
||||||
ModuleName: "api-service",
|
ModuleName: "api-service",
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
HasPagination: true,
|
HasPagination: true,
|
||||||
|
|||||||
0
tools/generete-handler.go.banckupnew
Normal file
0
tools/generete-handler.go.banckupnew
Normal file
Reference in New Issue
Block a user