diff --git a/docs/docs.go b/docs/docs.go index 50c510d..97a082c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,5 +1,4 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs Code generated by swaggo/swag. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -45,7 +44,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -53,7 +52,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -96,7 +95,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.User" + "$ref": "#/definitions/models.User" } }, "401": { @@ -142,7 +141,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -276,50 +275,6 @@ const docTemplate = `{ } } }, - "/api/v1/bpjs/reference/referensi/diagnosa": { - "get": { - "description": "Get all diagnosa reference data", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "bpjs/reference" - ], - "summary": "Get all diagnosa reference data", - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/models.DiagnosaResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "404": { - "description": "Data not found", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, "/api/v1/retribusi/{id}": { "get": { "description": "Returns a single retribusi by ID", @@ -346,25 +301,25 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse" + "$ref": "#/definitions/models.RetribusiGetByIDResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -395,7 +350,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest" + "$ref": "#/definitions/models.RetribusiUpdateRequest" } } ], @@ -403,25 +358,25 @@ const docTemplate = `{ "200": { "description": "Retribusi updated successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse" + "$ref": "#/definitions/models.RetribusiUpdateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -451,25 +406,25 @@ const docTemplate = `{ "200": { "description": "Retribusi deleted successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse" + "$ref": "#/definitions/models.RetribusiDeleteResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -539,19 +494,19 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/models.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -575,7 +530,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest" + "$ref": "#/definitions/models.RetribusiCreateRequest" } } ], @@ -583,19 +538,19 @@ const docTemplate = `{ "201": { "description": "Retribusi created successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse" + "$ref": "#/definitions/models.RetribusiCreateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -652,19 +607,19 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/models.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -695,13 +650,13 @@ const docTemplate = `{ "200": { "description": "Statistics data", "schema": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" + "$ref": "#/definitions/models.AggregateData" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -727,7 +682,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -735,7 +690,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -790,7 +745,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -807,7 +762,7 @@ const docTemplate = `{ }, "/sep": { "put": { - "description": "Update an existing Surat Eligibilitas Peserta", + "description": "Update Surat Eligibilitas Peserta", "consumes": [ "application/json" ], @@ -815,9 +770,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "bpjs" + "SEP" ], - "summary": "Update an existing SEP", + "summary": "Update SEP", "parameters": [ { "description": "SEP update request", @@ -859,7 +814,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "bpjs" + "SEP" ], "summary": "Create a new SEP", "parameters": [ @@ -905,9 +860,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "bpjs" + "SEP" ], - "summary": "Get an existing SEP", + "summary": "Get SEP", "parameters": [ { "type": "string", @@ -947,9 +902,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "bpjs" + "SEP" ], - "summary": "Delete an existing SEP", + "summary": "Delete SEP", "parameters": [ { "type": "string", @@ -990,7 +945,11 @@ const docTemplate = `{ } }, "definitions": { - "api-service_internal_models.AggregateData": { + "gin.H": { + "type": "object", + "additionalProperties": {} + }, + "models.AggregateData": { "type": "object", "properties": { "by_dinas": { @@ -1031,7 +990,7 @@ const docTemplate = `{ } } }, - "api-service_internal_models.ErrorResponse": { + "models.ErrorResponse": { "type": "object", "properties": { "code": { @@ -1048,396 +1007,13 @@ const docTemplate = `{ } } }, - "api-service_internal_models.MetaResponse": { - "type": "object", - "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" - } - } - }, - "api-service_internal_models.NullableInt32": { - "type": "object", - "properties": { - "int32": { - "type": "integer" - }, - "valid": { - "type": "boolean" - } - } - }, - "api-service_internal_models_auth.LoginRequest": { - "type": "object", - "required": [ - "password", - "username" - ], - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "api-service_internal_models_auth.TokenResponse": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - }, - "expires_in": { - "type": "integer" - }, - "token_type": { - "type": "string" - } - } - }, - "api-service_internal_models_auth.User": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "id": { - "type": "string" - }, - "role": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.Retribusi": { - "type": "object", - "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" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiDeleteResponse": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetByIDResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - } - }, - "message": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/api-service_internal_models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "gin.H": { - "type": "object", - "additionalProperties": {} - }, - "models.DiagnosaResponse": { - "type": "object", - "properties": { - "data": { - "type": "object", - "additionalProperties": true - }, - "message": { - "type": "string" - } - } - }, "models.Flag": { "type": "object", "required": [ - "cob" + "flag" ], "properties": { - "cob": { + "flag": { "type": "string" } } @@ -1496,6 +1072,21 @@ const docTemplate = `{ } } }, + "models.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "models.LokasiLaka": { "type": "object", "properties": { @@ -1510,6 +1101,43 @@ const docTemplate = `{ } } }, + "models.MetaResponse": { + "type": "object", + "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" + } + } + }, + "models.NullableInt32": { + "type": "object", + "properties": { + "int32": { + "type": "integer" + }, + "valid": { + "type": "boolean" + } + } + }, "models.Penjamin": { "type": "object", "properties": { @@ -1527,7 +1155,8 @@ const docTemplate = `{ "models.Poli": { "type": "object", "required": [ - "eksekutif" + "eksekutif", + "tujuan" ], "properties": { "eksekutif": { @@ -1538,6 +1167,290 @@ const docTemplate = `{ } } }, + "models.Retribusi": { + "type": "object", + "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" + } + } + }, + "models.RetribusiCreateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "dinas": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "jenis": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kelompok_obyek": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kode_tarif": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "pelayanan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_denda": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_pokok": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan_overtime": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "status": { + "type": "string", + "enum": [ + "draft", + "active", + "inactive" + ] + }, + "tarif": { + "type": "string" + }, + "tarif_overtime": { + "type": "string" + }, + "uraian_1": { + "type": "string" + }, + "uraian_2": { + "type": "string" + }, + "uraian_3": { + "type": "string" + } + } + }, + "models.RetribusiCreateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Retribusi" + }, + "message": { + "type": "string" + } + } + }, + "models.RetribusiDeleteResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "models.RetribusiGetByIDResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Retribusi" + }, + "message": { + "type": "string" + } + } + }, + "models.RetribusiGetResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Retribusi" + } + }, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/models.MetaResponse" + }, + "summary": { + "$ref": "#/definitions/models.AggregateData" + } + } + }, + "models.RetribusiUpdateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "dinas": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "jenis": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kelompok_obyek": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kode_tarif": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "pelayanan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_denda": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_pokok": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan_overtime": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "status": { + "type": "string", + "enum": [ + "draft", + "active", + "inactive" + ] + }, + "tarif": { + "type": "string" + }, + "tarif_overtime": { + "type": "string" + }, + "uraian_1": { + "type": "string" + }, + "uraian_2": { + "type": "string" + }, + "uraian_3": { + "type": "string" + } + } + }, + "models.RetribusiUpdateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Retribusi" + }, + "message": { + "type": "string" + } + } + }, "models.Rujukan": { "type": "object", "required": [ @@ -1564,10 +1477,10 @@ const docTemplate = `{ "models.SepPostRequest": { "type": "object", "required": [ - "t_sep" + "tsep" ], "properties": { - "t_sep": { + "tsep": { "$ref": "#/definitions/models.TSepPost" } } @@ -1575,10 +1488,10 @@ const docTemplate = `{ "models.SepPutRequest": { "type": "object", "required": [ - "t_sep" + "tsep" ], "properties": { - "t_sep": { + "tsep": { "$ref": "#/definitions/models.TSepPut" } } @@ -1754,6 +1667,37 @@ const docTemplate = `{ } } }, + "models.TokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "token_type": { + "type": "string" + } + } + }, + "models.User": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "role": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "sql.NullString": { "type": "object", "properties": { @@ -1791,6 +1735,8 @@ var SwaggerInfo = &swag.Spec{ Description: "A comprehensive Go API service with Swagger documentation", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", } func init() { diff --git a/docs/swagger.json b/docs/swagger.json index bdc7db5..20813f0 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -42,7 +42,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -50,7 +50,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -93,7 +93,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.User" + "$ref": "#/definitions/models.User" } }, "401": { @@ -139,7 +139,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -273,50 +273,6 @@ } } }, - "/api/v1/bpjs/reference/referensi/diagnosa": { - "get": { - "description": "Get all diagnosa reference data", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "bpjs/reference" - ], - "summary": "Get all diagnosa reference data", - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/models.DiagnosaResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "404": { - "description": "Data not found", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, "/api/v1/retribusi/{id}": { "get": { "description": "Returns a single retribusi by ID", @@ -343,25 +299,25 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse" + "$ref": "#/definitions/models.RetribusiGetByIDResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -392,7 +348,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest" + "$ref": "#/definitions/models.RetribusiUpdateRequest" } } ], @@ -400,25 +356,25 @@ "200": { "description": "Retribusi updated successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse" + "$ref": "#/definitions/models.RetribusiUpdateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -448,25 +404,25 @@ "200": { "description": "Retribusi deleted successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse" + "$ref": "#/definitions/models.RetribusiDeleteResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -536,19 +492,19 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/models.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -572,7 +528,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest" + "$ref": "#/definitions/models.RetribusiCreateRequest" } } ], @@ -580,19 +536,19 @@ "201": { "description": "Retribusi created successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse" + "$ref": "#/definitions/models.RetribusiCreateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -649,19 +605,19 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/models.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -692,13 +648,13 @@ "200": { "description": "Statistics data", "schema": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" + "$ref": "#/definitions/models.AggregateData" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -724,7 +680,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -732,7 +688,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -787,7 +743,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -804,7 +760,7 @@ }, "/sep": { "put": { - "description": "Update an existing Surat Eligibilitas Peserta", + "description": "Update Surat Eligibilitas Peserta", "consumes": [ "application/json" ], @@ -812,9 +768,9 @@ "application/json" ], "tags": [ - "bpjs" + "SEP" ], - "summary": "Update an existing SEP", + "summary": "Update SEP", "parameters": [ { "description": "SEP update request", @@ -856,7 +812,7 @@ "application/json" ], "tags": [ - "bpjs" + "SEP" ], "summary": "Create a new SEP", "parameters": [ @@ -902,9 +858,9 @@ "application/json" ], "tags": [ - "bpjs" + "SEP" ], - "summary": "Get an existing SEP", + "summary": "Get SEP", "parameters": [ { "type": "string", @@ -944,9 +900,9 @@ "application/json" ], "tags": [ - "bpjs" + "SEP" ], - "summary": "Delete an existing SEP", + "summary": "Delete SEP", "parameters": [ { "type": "string", @@ -987,7 +943,11 @@ } }, "definitions": { - "api-service_internal_models.AggregateData": { + "gin.H": { + "type": "object", + "additionalProperties": {} + }, + "models.AggregateData": { "type": "object", "properties": { "by_dinas": { @@ -1028,7 +988,7 @@ } } }, - "api-service_internal_models.ErrorResponse": { + "models.ErrorResponse": { "type": "object", "properties": { "code": { @@ -1045,396 +1005,13 @@ } } }, - "api-service_internal_models.MetaResponse": { - "type": "object", - "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" - } - } - }, - "api-service_internal_models.NullableInt32": { - "type": "object", - "properties": { - "int32": { - "type": "integer" - }, - "valid": { - "type": "boolean" - } - } - }, - "api-service_internal_models_auth.LoginRequest": { - "type": "object", - "required": [ - "password", - "username" - ], - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "api-service_internal_models_auth.TokenResponse": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - }, - "expires_in": { - "type": "integer" - }, - "token_type": { - "type": "string" - } - } - }, - "api-service_internal_models_auth.User": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "id": { - "type": "string" - }, - "role": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.Retribusi": { - "type": "object", - "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" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiDeleteResponse": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetByIDResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - } - }, - "message": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/api-service_internal_models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "gin.H": { - "type": "object", - "additionalProperties": {} - }, - "models.DiagnosaResponse": { - "type": "object", - "properties": { - "data": { - "type": "object", - "additionalProperties": true - }, - "message": { - "type": "string" - } - } - }, "models.Flag": { "type": "object", "required": [ - "cob" + "flag" ], "properties": { - "cob": { + "flag": { "type": "string" } } @@ -1493,6 +1070,21 @@ } } }, + "models.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "models.LokasiLaka": { "type": "object", "properties": { @@ -1507,6 +1099,43 @@ } } }, + "models.MetaResponse": { + "type": "object", + "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" + } + } + }, + "models.NullableInt32": { + "type": "object", + "properties": { + "int32": { + "type": "integer" + }, + "valid": { + "type": "boolean" + } + } + }, "models.Penjamin": { "type": "object", "properties": { @@ -1524,7 +1153,8 @@ "models.Poli": { "type": "object", "required": [ - "eksekutif" + "eksekutif", + "tujuan" ], "properties": { "eksekutif": { @@ -1535,6 +1165,290 @@ } } }, + "models.Retribusi": { + "type": "object", + "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" + } + } + }, + "models.RetribusiCreateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "dinas": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "jenis": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kelompok_obyek": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kode_tarif": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "pelayanan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_denda": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_pokok": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan_overtime": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "status": { + "type": "string", + "enum": [ + "draft", + "active", + "inactive" + ] + }, + "tarif": { + "type": "string" + }, + "tarif_overtime": { + "type": "string" + }, + "uraian_1": { + "type": "string" + }, + "uraian_2": { + "type": "string" + }, + "uraian_3": { + "type": "string" + } + } + }, + "models.RetribusiCreateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Retribusi" + }, + "message": { + "type": "string" + } + } + }, + "models.RetribusiDeleteResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "models.RetribusiGetByIDResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Retribusi" + }, + "message": { + "type": "string" + } + } + }, + "models.RetribusiGetResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Retribusi" + } + }, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/models.MetaResponse" + }, + "summary": { + "$ref": "#/definitions/models.AggregateData" + } + } + }, + "models.RetribusiUpdateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "dinas": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "jenis": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kelompok_obyek": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "kode_tarif": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "pelayanan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_denda": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "rekening_pokok": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "satuan_overtime": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "status": { + "type": "string", + "enum": [ + "draft", + "active", + "inactive" + ] + }, + "tarif": { + "type": "string" + }, + "tarif_overtime": { + "type": "string" + }, + "uraian_1": { + "type": "string" + }, + "uraian_2": { + "type": "string" + }, + "uraian_3": { + "type": "string" + } + } + }, + "models.RetribusiUpdateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Retribusi" + }, + "message": { + "type": "string" + } + } + }, "models.Rujukan": { "type": "object", "required": [ @@ -1561,10 +1475,10 @@ "models.SepPostRequest": { "type": "object", "required": [ - "t_sep" + "tsep" ], "properties": { - "t_sep": { + "tsep": { "$ref": "#/definitions/models.TSepPost" } } @@ -1572,10 +1486,10 @@ "models.SepPutRequest": { "type": "object", "required": [ - "t_sep" + "tsep" ], "properties": { - "t_sep": { + "tsep": { "$ref": "#/definitions/models.TSepPut" } } @@ -1751,6 +1665,37 @@ } } }, + "models.TokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "token_type": { + "type": "string" + } + } + }, + "models.User": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "role": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "sql.NullString": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 721069e..a9d9b4f 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,6 +1,9 @@ basePath: /api/v1 definitions: - api-service_internal_models.AggregateData: + gin.H: + additionalProperties: {} + type: object + models.AggregateData: properties: by_dinas: additionalProperties: @@ -27,7 +30,7 @@ definitions: updated_today: type: integer type: object - api-service_internal_models.ErrorResponse: + models.ErrorResponse: properties: code: type: integer @@ -38,277 +41,12 @@ definitions: timestamp: type: string 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: properties: - cob: + flag: type: string required: - - cob + - flag type: object models.Jaminan: properties: @@ -345,6 +83,16 @@ definitions: penanggungJawab: type: string type: object + models.LoginRequest: + properties: + password: + type: string + username: + type: string + required: + - password + - username + type: object models.LokasiLaka: properties: kdKabupaten: @@ -354,6 +102,30 @@ definitions: kdPropinsi: type: string 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: properties: keterangan: @@ -371,6 +143,207 @@ definitions: type: string required: - 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 models.Rujukan: properties: @@ -390,17 +363,17 @@ definitions: type: object models.SepPostRequest: properties: - t_sep: + tsep: $ref: '#/definitions/models.TSepPost' required: - - t_sep + - tsep type: object models.SepPutRequest: properties: - t_sep: + tsep: $ref: '#/definitions/models.TSepPut' required: - - t_sep + - tsep type: object models.SepResponse: properties: @@ -520,6 +493,26 @@ definitions: - noSep - user 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: properties: string: @@ -561,14 +554,14 @@ paths: name: login required: true schema: - $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' + $ref: '#/definitions/models.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -593,7 +586,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.User' + $ref: '#/definitions/models.User' "401": description: Unauthorized schema: @@ -625,7 +618,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -715,36 +708,6 @@ paths: summary: Get participant data by NIK tags: - 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}: delete: consumes: @@ -762,19 +725,19 @@ paths: "200": description: Retribusi deleted successfully schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse' + $ref: '#/definitions/models.RetribusiDeleteResponse' "400": description: Invalid ID format schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Delete retribusi tags: - retribusi @@ -794,19 +757,19 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse' + $ref: '#/definitions/models.RetribusiGetByIDResponse' "400": description: Invalid ID format schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get Retribusi by ID tags: - retribusi @@ -825,26 +788,26 @@ paths: name: request required: true schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest' + $ref: '#/definitions/models.RetribusiUpdateRequest' produces: - application/json responses: "200": description: Retribusi updated successfully schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse' + $ref: '#/definitions/models.RetribusiUpdateResponse' "400": description: Bad request or validation error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Update retribusi tags: - retribusi @@ -891,15 +854,15 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse' + $ref: '#/definitions/models.RetribusiGetResponse' "400": description: Bad request schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get retribusi with pagination and optional aggregation tags: - retribusi @@ -913,22 +876,22 @@ paths: name: request required: true schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest' + $ref: '#/definitions/models.RetribusiCreateRequest' produces: - application/json responses: "201": description: Retribusi created successfully schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse' + $ref: '#/definitions/models.RetribusiCreateResponse' "400": description: Bad request or validation error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Create retribusi tags: - retribusi @@ -966,15 +929,15 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse' + $ref: '#/definitions/models.RetribusiGetResponse' "400": description: Bad request schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get retribusi with dynamic filtering tags: - retribusi @@ -994,11 +957,11 @@ paths: "200": description: Statistics data schema: - $ref: '#/definitions/api-service_internal_models.AggregateData' + $ref: '#/definitions/models.AggregateData' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get retribusi statistics tags: - retribusi @@ -1013,14 +976,14 @@ paths: name: token required: true schema: - $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' + $ref: '#/definitions/models.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -1057,7 +1020,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -1096,11 +1059,11 @@ paths: $ref: '#/definitions/gin.H' summary: Create a new SEP tags: - - bpjs + - SEP put: consumes: - application/json - description: Update an existing Surat Eligibilitas Peserta + description: Update Surat Eligibilitas Peserta parameters: - description: SEP update request in: body @@ -1123,9 +1086,9 @@ paths: description: Internal server error schema: $ref: '#/definitions/gin.H' - summary: Update an existing SEP + summary: Update SEP tags: - - bpjs + - SEP /sep/{noSep}: delete: consumes: @@ -1157,9 +1120,9 @@ paths: description: Internal server error schema: $ref: '#/definitions/gin.H' - summary: Delete an existing SEP + summary: Delete SEP tags: - - bpjs + - SEP get: consumes: - application/json @@ -1185,9 +1148,9 @@ paths: description: Internal server error schema: $ref: '#/definitions/gin.H' - summary: Get an existing SEP + summary: Get SEP tags: - - bpjs + - SEP schemes: - http - https diff --git a/example.env b/example.env index 537853c..6c3222f 100644 --- a/example.env +++ b/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_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_KFA_URL=https://api-satusehat.kemkes.go.id/kfa-v2 \ No newline at end of file +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 diff --git a/examples/satusehat_specific_requests.go b/examples/satusehat_specific_requests.go new file mode 100644 index 0000000..730f5f6 --- /dev/null +++ b/examples/satusehat_specific_requests.go @@ -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(" -----------------------") +} diff --git a/go.mod b/go.mod index 32e6655..4acde4b 100644 --- a/go.mod +++ b/go.mod @@ -21,9 +21,10 @@ require ( github.com/go-sql-driver/mysql v1.8.1 github.com/joho/godotenv v1.5.1 github.com/mashingan/smapping v0.1.19 + github.com/rs/zerolog v1.34.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 - github.com/swaggo/swag v1.16.6 + github.com/tidwall/gjson v1.18.0 ) require ( @@ -57,6 +58,7 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // 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/microsoft/go-mssqldb v1.8.2 // 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/pelletier/go-toml/v2 v2.2.4 // 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/ugorji/go/codec v1.3.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect diff --git a/go.sum b/go.sum index 79eda12..5e9b6d8 100644 --- a/go.sum +++ b/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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= 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/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= @@ -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/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/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.2.1/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/mashingan/smapping v0.1.19 h1:SsEtuPn2UcM1croIupPtGLgWgpYRuS0rSQMvKD9g2BQ= 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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 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/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.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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= 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/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 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-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-20220811171246-fbc7d0a398ab/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/config/config.go b/internal/config/config.go index d2a3311..7c899e2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,22 @@ type Config struct { Keycloak KeycloakConfig Bpjs BpjsConfig 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 { @@ -141,6 +157,20 @@ func LoadConfig() *Config { KFAURL: getEnv("BRIDGING_SATUSEHAT_KFA_URL", "https://api-satusehat.kemkes.go.id/kfa-v2"), 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 @@ -600,6 +630,19 @@ func getEnvAsBool(key string, defaultValue bool) bool { 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 { if len(c.Databases) == 0 { log.Fatal("At least one database configuration is required") diff --git a/internal/handlers/bpjs/reference/diagnosa.go b/internal/handlers/bpjs/reference/diagnosa.go deleted file mode 100644 index a23ab4e..0000000 --- a/internal/handlers/bpjs/reference/diagnosa.go +++ /dev/null @@ -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, - } -} diff --git a/internal/handlers/healthcheck/healthcheck.go b/internal/handlers/healthcheck/healthcheck.go new file mode 100644 index 0000000..d109bff --- /dev/null +++ b/internal/handlers/healthcheck/healthcheck.go @@ -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) +} diff --git a/internal/handlers/bpjs/reference/peserta.go b/internal/handlers/reference/peserta.go similarity index 100% rename from internal/handlers/bpjs/reference/peserta.go rename to internal/handlers/reference/peserta.go diff --git a/internal/handlers/satusehat/patient_handler.go b/internal/handlers/satusehat/patient_handler.go deleted file mode 100644 index 7f2f09d..0000000 --- a/internal/handlers/satusehat/patient_handler.go +++ /dev/null @@ -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, - }, - }) -} diff --git a/internal/handlers/swagger/swagger.go b/internal/handlers/swagger/swagger.go new file mode 100644 index 0000000..a8f2a95 --- /dev/null +++ b/internal/handlers/swagger/swagger.go @@ -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{}{}, + }, + }) +} diff --git a/internal/handlers/bpjs/vclaim/sep.go b/internal/handlers/vclaim/sep.go similarity index 57% rename from internal/handlers/bpjs/vclaim/sep.go rename to internal/handlers/vclaim/sep.go index c6d1ddd..a1a4c8a 100644 --- a/internal/handlers/bpjs/vclaim/sep.go +++ b/internal/handlers/vclaim/sep.go @@ -7,8 +7,7 @@ import ( "time" "api-service/internal/config" - models "api-service/internal/models/bpjs/vclaim" - + vclaimModels "api-service/internal/models/vclaim" services "api-service/internal/services/bpjs" "github.com/gin-gonic/gin" @@ -27,18 +26,22 @@ func NewSepHandler(cfg config.BpjsConfig) *SepHandler { // CreateSEP godoc // @Summary Create a new SEP // @Description Create a new Surat Eligibilitas Peserta -// @Tags bpjs +// @Tags SEP // @Accept json // @Produce json -// @Param request body models.SepPostRequest true "SEP creation request" -// @Success 200 {object} models.SepResponse "SEP created successfully" +// @Param request body vclaimModels.SepPostRequest true "SEP creation request" +// @Success 200 {object} vclaimModels.SepResponse "SEP created successfully" // @Failure 400 {object} gin.H "Invalid request" // @Failure 500 {object} gin.H "Internal server error" // @Router /sep [post] func (h *SepHandler) CreateSEP(c *gin.Context) { - var req models.SepPostRequest + var req vclaimModels.SepPostRequest + 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 } @@ -46,32 +49,39 @@ func (h *SepHandler) CreateSEP(c *gin.Context) { defer cancel() var result map[string]interface{} - 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()}) + 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(), + }) return } - c.JSON(http.StatusOK, models.SepResponse{ + c.JSON(http.StatusOK, vclaimModels.SepResponse{ Message: "SEP berhasil dibuat", Data: result, }) } // UpdateSEP godoc -// @Summary Update an existing SEP -// @Description Update an existing Surat Eligibilitas Peserta -// @Tags bpjs +// @Summary Update SEP +// @Description Update Surat Eligibilitas Peserta +// @Tags SEP // @Accept json // @Produce json -// @Param request body models.SepPutRequest true "SEP update request" -// @Success 200 {object} models.SepResponse "SEP updated successfully" +// @Param request body vclaimModels.SepPutRequest true "SEP update request" +// @Success 200 {object} vclaimModels.SepResponse "SEP updated successfully" // @Failure 400 {object} gin.H "Invalid request" // @Failure 500 {object} gin.H "Internal server error" // @Router /sep [put] func (h *SepHandler) UpdateSEP(c *gin.Context) { - var req models.SepPutRequest + var req vclaimModels.SepPutRequest + 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 } @@ -79,85 +89,101 @@ func (h *SepHandler) UpdateSEP(c *gin.Context) { defer cancel() var result map[string]interface{} - 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()}) + 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(), + }) return } - c.JSON(http.StatusOK, models.SepResponse{ + c.JSON(http.StatusOK, vclaimModels.SepResponse{ Message: "SEP berhasil diperbarui", Data: result, }) } // DeleteSEP godoc -// @Summary Delete an existing SEP +// @Summary Delete SEP // @Description Delete a Surat Eligibilitas Peserta by noSep -// @Tags bpjs +// @Tags SEP // @Accept json // @Produce json // @Param noSep path string true "No SEP" // @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 500 {object} gin.H "Internal server error" // @Router /sep/{noSep} [delete] func (h *SepHandler) DeleteSEP(c *gin.Context) { noSep := c.Param("noSep") user := c.Query("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 } - body := models.SepDeleteRequest{} + body := vclaimModels.SepDeleteRequest{} body.TSep.NoSep = noSep body.TSep.User = user ctx, cancel := context.WithTimeout(c, 30*time.Second) defer cancel() - 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()}) + 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(), + }) return } var result map[string]interface{} - c.JSON(http.StatusOK, models.SepResponse{ + c.JSON(http.StatusOK, vclaimModels.SepResponse{ Message: "SEP berhasil dihapus", Data: result, }) } // GetSEP godoc -// @Summary Get an existing SEP +// @Summary Get SEP // @Description Retrieve a Surat Eligibilitas Peserta by noSep -// @Tags bpjs +// @Tags SEP // @Accept json // @Produce json // @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 500 {object} gin.H "Internal server error" // @Router /sep/{noSep} [get] func (h *SepHandler) GetSEP(c *gin.Context) { noSep := c.Param("noSep") + if noSep == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "noSep required"}) + c.JSON(http.StatusBadRequest, gin.H{ + "error": "noSep required", + }) return } ctx, cancel := context.WithTimeout(c, 30*time.Second) defer cancel() - endpoint := fmt.Sprintf("/SEP/%s", noSep) + endpoint := fmt.Sprintf("SEP/%s", noSep) var result map[string]interface{} + 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 } - c.JSON(http.StatusOK, models.SepResponse{ + c.JSON(http.StatusOK, vclaimModels.SepResponse{ Message: "Data SEP berhasil diambil", Data: result, }) diff --git a/internal/models/bpjs/reference/diagnosa.go b/internal/models/bpjs/reference/diagnosa.go deleted file mode 100644 index 98cf457..0000000 --- a/internal/models/bpjs/reference/diagnosa.go +++ /dev/null @@ -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"` -} diff --git a/internal/models/bpjs/reference/peserta.go b/internal/models/reference/peserta.go similarity index 100% rename from internal/models/bpjs/reference/peserta.go rename to internal/models/reference/peserta.go diff --git a/internal/models/bpjs/vclaim/sep.go b/internal/models/vclaim/sep.go similarity index 69% rename from internal/models/bpjs/vclaim/sep.go rename to internal/models/vclaim/sep.go index d381cd0..821d128 100644 --- a/internal/models/bpjs/vclaim/sep.go +++ b/internal/models/vclaim/sep.go @@ -1,25 +1,25 @@ package models -// ==== REQUEST ==== - +// SepPostRequest represents the request payload for creating a SEP 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 { 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"` JnsPelayanan string `json:"jnsPelayanan" binding:"required"` KlsRawat KlsRawatPost `json:"klsRawat" binding:"required"` NoMR string `json:"noMR" binding:"required"` - Rujukan Rujukan `json:"rujukan" binding:"required"` + Rujukan Rujukan `json:"rujukan" binding:"required"` Catatan string `json:"catatan"` DiagAwal string `json:"diagAwal" binding:"required"` Poli Poli `json:"poli" binding:"required"` - Cob Flag `json:"cob" binding:"required"` - Katarak Flag `json:"katarak" binding:"required"` - Jaminan Jaminan `json:"jaminan" binding:"required"` + Cob Flag `json:"cob" binding:"required"` + Katarak Flag `json:"katarak" binding:"required"` + Jaminan Jaminan `json:"jaminan" binding:"required"` TujuanKunj string `json:"tujuanKunj"` FlagProcedure string `json:"flagProcedure"` KdPenunjang string `json:"kdPenunjang"` @@ -30,6 +30,7 @@ type TSepPost struct { User string `json:"user" binding:"required"` } +// KlsRawatPost represents class of care data for POST requests type KlsRawatPost struct { KlsRawatHak string `json:"klsRawatHak" binding:"required"` KlsRawatNaik string `json:"klsRawatNaik"` @@ -37,57 +38,65 @@ type KlsRawatPost struct { PenanggungJawab string `json:"penanggungJawab"` } +// Rujukan represents referral data type Rujukan struct { AsalRujukan string `json:"asalRujukan" 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"` } +// Poli represents poly/department data type Poli struct { - Tujuan string `json:"tujuan"` + Tujuan string `json:"tujuan" binding:"required"` Eksekutif string `json:"eksekutif" binding:"required"` } +// Flag represents a generic flag structure 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 { LakaLantas string `json:"lakaLantas" binding:"required"` NoLP string `json:"noLP"` Penjamin Penjamin `json:"penjamin"` } +// Penjamin represents guarantor data type Penjamin struct { TglKejadian string `json:"tglKejadian"` Keterangan string `json:"keterangan"` Suplesi Suplesi `json:"suplesi"` } +// Suplesi represents supplementary data type Suplesi struct { Suplesi string `json:"suplesi"` NoSepSuplesi string `json:"noSepSuplesi"` LokasiLaka LokasiLaka `json:"lokasiLaka"` } +// LokasiLaka represents accident location data type LokasiLaka struct { KdPropinsi string `json:"kdPropinsi"` KdKabupaten string `json:"kdKabupaten"` KdKecamatan string `json:"kdKecamatan"` } +// Skdp represents SKDP data type Skdp struct { NoSurat string `json:"noSurat" binding:"required"` KodeDPJP string `json:"kodeDPJP" binding:"required"` } -// ==== UPDATE ==== - +// SepPutRequest represents the request payload for updating a SEP 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 { NoSep string `json:"noSep" binding:"required"` KlsRawat KlsRawatPut `json:"klsRawat"` @@ -103,6 +112,7 @@ type TSepPut struct { User string `json:"user" binding:"required"` } +// KlsRawatPut represents class of care data for PUT requests type KlsRawatPut struct { KlsRawatHak string `json:"klsRawatHak"` KlsRawatNaik string `json:"klsRawatNaik"` @@ -110,22 +120,21 @@ type KlsRawatPut struct { PenanggungJawab string `json:"penanggungJawab"` } -// ==== DELETE ==== - +// SepDeleteRequest represents the request payload for deleting a SEP type SepDeleteRequest struct { TSep struct { NoSep string `json:"noSep" binding:"required"` - User string `json:"user" binding:"required"` - } `json:"t_sep" binding:"required"` + User string `json:"user" binding:"required"` + } `json:"tsep" binding:"required"` } -// ==== RESPONSE ==== - +// SepResponse represents the standard response for SEP operations type SepResponse struct { Message string `json:"message"` Data map[string]interface{} `json:"data,omitempty"` } +// SepRawResponse represents the raw response from BPJS API type SepRawResponse struct { MetaData struct { Code string `json:"code"` diff --git a/internal/routes/v1/routes.go b/internal/routes/v1/routes.go index 5ea2965..16b21b9 100644 --- a/internal/routes/v1/routes.go +++ b/internal/routes/v1/routes.go @@ -2,18 +2,17 @@ package v1 import ( "api-service/internal/config" + "api-service/internal/database" 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" - satusehatHandlers "api-service/internal/handlers/satusehat" + swaggerHandlers "api-service/internal/handlers/swagger" "api-service/internal/middleware" services "api-service/internal/services/auth" - satusehatServices "api-service/internal/services/satusehat" "api-service/pkg/logger" "github.com/gin-gonic/gin" - swaggerFiles "github.com/swaggo/files" - ginSwagger "github.com/swaggo/gin-swagger" ) 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") } - // Initialize SatuSehat service - satusehatService := satusehatServices.NewSatuSehatService(&cfg.SatuSehat) - if satusehatService == nil { - logger.Fatal("Failed to initialize SatuSehat service") - } + // Initialize database service for health check + dbService := database.New(cfg) - // Swagger UI route - router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + // Health check endpoint + 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 v1 := router.Group("/api/v1") @@ -67,16 +71,6 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs) v1.GET("/bpjs/peserta/nik/:nik/tglSEP/:tglSEP", bpjsPesertaHandler.GetPesertaByNIK) - // SatuSehat endpoints - satusehatPatientHandler := satusehatHandlers.NewPatientHandler(satusehatService) - satusehatGroup := v1.Group("/satusehat") - { - satusehatGroup.GET("/patient/search/nik", satusehatPatientHandler.SearchPatientByNIK) - satusehatGroup.GET("/patient/search/name", satusehatPatientHandler.SearchPatientByName) - satusehatGroup.POST("/patient", satusehatPatientHandler.CreatePatient) - satusehatGroup.GET("/token", satusehatPatientHandler.GetAccessToken) - } - // ============= PUBLISHED ROUTES =============================================== // // Retribusi endpoints diff --git a/internal/services/bpjs/vclaimBridge.go b/internal/services/bpjs/vclaimBridge.go index 4a386dd..b102762 100644 --- a/internal/services/bpjs/vclaimBridge.go +++ b/internal/services/bpjs/vclaimBridge.go @@ -12,6 +12,8 @@ import ( "api-service/internal/config" "github.com/mashingan/smapping" + "github.com/rs/zerolog/log" + "github.com/tidwall/gjson" ) // VClaimService interface for VClaim operations @@ -49,6 +51,11 @@ type ResponDTO struct { // NewService creates a new VClaim service instance 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{ config: cfg, httpClient: &http.Client{ @@ -88,8 +95,20 @@ func (s *Service) SetHTTPClient(client *http.Client) { // prepareRequest prepares HTTP request with required headers func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, body io.Reader) (*http.Request, error) { 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) 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) } @@ -102,6 +121,14 @@ func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, b req.Header.Set("X-signature", xSignature) 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 } @@ -109,22 +136,55 @@ func (s *Service) prepareRequest(ctx context.Context, method, endpoint string, b func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) { 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) 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) } + // 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 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)) } // Parse raw response var respMentah ResponMentahDTO 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) } + // Log metadata + log.Info(). + Str("meta_code", respMentah.MetaData.Code). + Str("meta_message", respMentah.MetaData.Message). + Msg("Response metadata") + // Create final response finalResp := &ResponDTO{ MetaData: respMentah.MetaData, @@ -132,6 +192,7 @@ func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) { // If response is empty, return as is if respMentah.Response == "" { + log.Debug().Msg("Empty response received, returning metadata only") return finalResp, nil } @@ -139,17 +200,47 @@ func (s *Service) processResponse(res *http.Response) (*ResponDTO, error) { consID, secretKey, _, tstamp, _ := s.config.SetHeader() respDecrypt, err := ResponseVclaim(respMentah.Response, consID+secretKey+tstamp) 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) } + 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 if respDecrypt != "" { if err := json.Unmarshal([]byte(respDecrypt), &finalResp.Response); err != nil { // If JSON unmarshal fails, store as string + log.Warn(). + Err(err). + Msg("Failed to unmarshal decrypted response, storing as string") 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 } diff --git a/internal/services/satusehat/satusehatBridge.go b/internal/services/satusehat/satusehatBridge.go new file mode 100644 index 0000000..01d1558 --- /dev/null +++ b/internal/services/satusehat/satusehatBridge.go @@ -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 +} diff --git a/internal/services/satusehat/satusehat_service.go b/internal/services/satusehat/satusehat_service.go deleted file mode 100644 index 988ca3d..0000000 --- a/internal/services/satusehat/satusehat_service.go +++ /dev/null @@ -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 "" -} diff --git a/test_swagger_env.go b/test_swagger_env.go new file mode 100644 index 0000000..8dcde3f --- /dev/null +++ b/test_swagger_env.go @@ -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") +} diff --git a/tools/general/generate-handler.go b/tools/general/generate-handler.go index 205c7bb..78cc6d1 100644 --- a/tools/general/generate-handler.go +++ b/tools/general/generate-handler.go @@ -13,8 +13,7 @@ type HandlerData struct { Name string NameLower string NamePlural string - Category string - CategoryPath string + Category string // Untuk backward compatibility (bagian pertama) ModuleName string TableName string HasGet bool @@ -48,16 +47,18 @@ func main() { 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 if strings.Contains(entityPath, "/") { parts := strings.Split(entityPath, "/") - if len(parts) != 2 { - fmt.Println("❌ Error: Invalid path format. Use 'category/entity' or just 'entity'") + if len(parts) < 2 || len(parts) > 4 { + fmt.Println("❌ Error: Invalid path format. Use up to 4 levels like 'level1/level2/level3/entity'") os.Exit(1) } + // Entity name is the last part + entityName = parts[len(parts)-1] + // Category for backward compatibility (first part) category = parts[0] - entityName = parts[1] } else { category = "" entityName = entityPath @@ -81,7 +82,6 @@ func main() { NameLower: entityLower, NamePlural: entityPlural, Category: category, - CategoryPath: category, ModuleName: "api-service", TableName: tableName, HasPagination: true, diff --git a/tools/generete-handler.go.banckupnew b/tools/generete-handler.go.banckupnew new file mode 100644 index 0000000..e69de29